diff options
Diffstat (limited to 'resources/src/mediawiki.libs')
-rw-r--r-- | resources/src/mediawiki.libs/CLDRPluralRuleParser.js | 246 |
1 files changed, 183 insertions, 63 deletions
diff --git a/resources/src/mediawiki.libs/CLDRPluralRuleParser.js b/resources/src/mediawiki.libs/CLDRPluralRuleParser.js index 83c25245..31c8fef9 100644 --- a/resources/src/mediawiki.libs/CLDRPluralRuleParser.js +++ b/resources/src/mediawiki.libs/CLDRPluralRuleParser.js @@ -1,12 +1,13 @@ -/* This is CLDRPluralRuleParser v1.1, ported to MediaWiki ResourceLoader */ +/* This is CLDRPluralRuleParser v1.1.3, ported to MediaWiki ResourceLoader */ /** * CLDRPluralRuleParser.js * A parser engine for CLDR plural rules. * -* Copyright 2012 GPLV3+, Santhosh Thottingal +* Copyright 2012-2014 Santhosh Thottingal and other contributors +* Released under the MIT license +* http://opensource.org/licenses/MIT * -* @version 0.1.0-alpha * @source https://github.com/santhoshtr/CLDRPluralRuleParser * @author Santhosh Thottingal <santhosh.thottingal@gmail.com> * @author Timo Tijhof @@ -22,6 +23,8 @@ */ function pluralRuleParser(rule, number) { + 'use strict'; + /* Syntax: see http://unicode.org/reports/tr35/#Language_Plural_Rules ----------------------------------------------------------------- @@ -44,14 +47,15 @@ function pluralRuleParser(rule, number) { decimalValue = value ('.' value)? */ - // we don't evaluate the samples section of the rule. Ignore it. + // We don't evaluate the samples section of the rule. Ignore it. rule = rule.split('@')[0].replace(/^\s*/, '').replace(/\s*$/, ''); if (!rule.length) { - // empty rule or 'other' rule. + // Empty rule or 'other' rule. return true; } - // Indicates current position in the rule as we parse through it. + + // Indicates the current position in the rule as we parse through it. // Shared among all parsing functions below. var pos = 0, operand, @@ -87,15 +91,18 @@ function pluralRuleParser(rule, number) { debug('pluralRuleParser', rule, number); // Try parsers until one works, if none work return null - function choice(parserSyntax) { return function() { - for (var i = 0; i < parserSyntax.length; i++) { - var result = parserSyntax[i](); + var i, result; + + for (i = 0; i < parserSyntax.length; i++) { + result = parserSyntax[i](); + if (result !== null) { return result; } } + return null; }; } @@ -103,46 +110,56 @@ function pluralRuleParser(rule, number) { // Try several parserSyntax-es in a row. // All must succeed; otherwise, return null. // This is the only eager one. - function sequence(parserSyntax) { - var originalPos = pos; - var result = []; - for (var i = 0; i < parserSyntax.length; i++) { - var res = parserSyntax[i](); - if (res === null) { + var i, parserRes, + originalPos = pos, + result = []; + + for (i = 0; i < parserSyntax.length; i++) { + parserRes = parserSyntax[i](); + + if (parserRes === null) { pos = originalPos; + return null; } - result.push(res); + + result.push(parserRes); } + return result; } // Run the same parser over and over until it fails. // Must succeed a minimum of n times; otherwise, return null. - function nOrMore(n, p) { return function() { - var originalPos = pos; - var result = []; - var parsed = p(); + var originalPos = pos, + result = [], + parsed = p(); + while (parsed !== null) { result.push(parsed); parsed = p(); } + if (result.length < n) { pos = originalPos; + return null; } + return result; }; } - // Helpers -- just make parserSyntax out of simpler JS builtin types + // Helpers - just make parserSyntax out of simpler JS builtin types function makeStringParser(s) { var len = s.length; + return function() { var result = null; + if (rule.substr(pos, len) === s) { result = s; pos += len; @@ -155,95 +172,122 @@ function pluralRuleParser(rule, number) { function makeRegexParser(regex) { return function() { var matches = rule.substr(pos).match(regex); + if (matches === null) { return null; } + pos += matches[0].length; + return matches[0]; }; } - /* - * integer digits of n. + /** + * Integer digits of n. */ function i() { var result = _i_(); + if (result === null) { debug(' -- failed i', parseInt(number, 10)); + return result; } + result = parseInt(number, 10); debug(' -- passed i ', result); + return result; } - /* - * absolute value of the source number (integer and decimals). + /** + * Absolute value of the source number (integer and decimals). */ function n() { var result = _n_(); + if (result === null) { debug(' -- failed n ', number); + return result; } + result = parseFloat(number, 10); debug(' -- passed n ', result); + return result; } - /* - * visible fractional digits in n, with trailing zeros. + /** + * Visible fractional digits in n, with trailing zeros. */ function f() { var result = _f_(); + if (result === null) { debug(' -- failed f ', number); + return result; } + result = (number + '.').split('.')[1] || 0; debug(' -- passed f ', result); + return result; } - /* - * visible fractional digits in n, without trailing zeros. + /** + * Visible fractional digits in n, without trailing zeros. */ function t() { var result = _t_(); + if (result === null) { debug(' -- failed t ', number); + return result; } + result = (number + '.').split('.')[1].replace(/0$/, '') || 0; debug(' -- passed t ', result); + return result; } - /* - * number of visible fraction digits in n, with trailing zeros. + /** + * Number of visible fraction digits in n, with trailing zeros. */ function v() { var result = _v_(); + if (result === null) { debug(' -- failed v ', number); + return result; } + result = (number + '.').split('.')[1].length || 0; debug(' -- passed v ', result); + return result; } - /* - * number of visible fraction digits in n, without trailing zeros. + /** + * Number of visible fraction digits in n, without trailing zeros. */ function w() { var result = _w_(); + if (result === null) { debug(' -- failed w ', number); + return result; } + result = (number + '.').split('.')[1].replace(/0$/, '').length || 0; debug(' -- passed w ', result); + return result; } @@ -254,19 +298,27 @@ function pluralRuleParser(rule, number) { expression = choice([mod, operand]); function mod() { - var result = sequence([operand, whitespace, choice([_mod_, _percent_]), whitespace, value]); + var result = sequence( + [operand, whitespace, choice([_mod_, _percent_]), whitespace, value] + ); + if (result === null) { debug(' -- failed mod'); + return null; } + debug(' -- passed ' + parseInt(result[0], 10) + ' ' + result[2] + ' ' + parseInt(result[4], 10)); + return parseInt(result[0], 10) % parseInt(result[4], 10); } function not() { var result = sequence([whitespace, _not_]); + if (result === null) { debug(' -- failed not'); + return null; } @@ -276,120 +328,170 @@ function pluralRuleParser(rule, number) { // is_relation = expr 'is' ('not')? value function is() { var result = sequence([expression, whitespace, choice([_is_]), whitespace, value]); + if (result !== null) { debug(' -- passed is : ' + result[0] + ' == ' + parseInt(result[4], 10)); + return result[0] === parseInt(result[4], 10); } + debug(' -- failed is'); + return null; } // is_relation = expr 'is' ('not')? value function isnot() { - var result = sequence([expression, whitespace, choice([_isnot_, _isnot_sign_]), whitespace, value]); + var result = sequence( + [expression, whitespace, choice([_isnot_, _isnot_sign_]), whitespace, value] + ); + if (result !== null) { debug(' -- passed isnot: ' + result[0] + ' != ' + parseInt(result[4], 10)); + return result[0] !== parseInt(result[4], 10); } + debug(' -- failed isnot'); + return null; } function not_in() { - var result = sequence([expression, whitespace, _isnot_sign_, whitespace, rangeList]); + var i, range_list, + result = sequence([expression, whitespace, _isnot_sign_, whitespace, rangeList]); + if (result !== null) { debug(' -- passed not_in: ' + result[0] + ' != ' + result[4]); - var range_list = result[4]; - for (var i = 0; i < range_list.length; i++) { + range_list = result[4]; + + for (i = 0; i < range_list.length; i++) { if (parseInt(range_list[i], 10) === parseInt(result[0], 10)) { return false; } } + return true; } + debug(' -- failed not_in'); + return null; } // range_list = (range | value) (',' range_list)* function rangeList() { - var result = sequence([choice([range, value]), nOrMore(0, rangeTail)]); - var resultList = []; + var result = sequence([choice([range, value]), nOrMore(0, rangeTail)]), + resultList = []; + if (result !== null) { resultList = resultList.concat(result[0]); + if (result[1][0]) { resultList = resultList.concat(result[1][0]); } + return resultList; } + debug(' -- failed rangeList'); + return null; } function rangeTail() { // ',' range_list var result = sequence([_comma_, rangeList]); + if (result !== null) { return result[1]; } + debug(' -- failed rangeTail'); + return null; } // range = value'..'value - function range() { - var i; - var result = sequence([value, _range_, value]); + var i, array, left, right, + result = sequence([value, _range_, value]); + if (result !== null) { debug(' -- passed range'); - var array = []; - var left = parseInt(result[0], 10); - var right = parseInt(result[2], 10); + + array = []; + left = parseInt(result[0], 10); + right = parseInt(result[2], 10); + for (i = left; i <= right; i++) { array.push(i); } + return array; } + debug(' -- failed range'); + return null; } function _in() { + var result, range_list, i; + // in_relation = expr ('not')? 'in' range_list - var result = sequence([expression, nOrMore(0, not), whitespace, choice([_in_, _equal_]), whitespace, rangeList]); + result = sequence( + [expression, nOrMore(0, not), whitespace, choice([_in_, _equal_]), whitespace, rangeList] + ); + if (result !== null) { debug(' -- passed _in:' + result); - var range_list = result[5]; - for (var i = 0; i < range_list.length; i++) { + + range_list = result[5]; + + for (i = 0; i < range_list.length; i++) { if (parseInt(range_list[i], 10) === parseInt(result[0], 10)) { return (result[1][0] !== 'not'); } } + return (result[1][0] === 'not'); } + debug(' -- failed _in '); + return null; } - /* - * The difference between in and within is that in only includes integers in the specified range, - * while within includes all values. + /** + * The difference between "in" and "within" is that + * "in" only includes integers in the specified range, + * while "within" includes all values. */ - function within() { + var range_list, result; + // within_relation = expr ('not')? 'within' range_list - var result = sequence([expression, nOrMore(0, not), whitespace, _within_, whitespace, rangeList]); + result = sequence( + [expression, nOrMore(0, not), whitespace, _within_, whitespace, rangeList] + ); + if (result !== null) { debug(' -- passed within'); - var range_list = result[5]; + + range_list = result[5]; + if ((result[0] >= parseInt(range_list[0], 10)) && (result[0] < parseInt(range_list[range_list.length - 1], 10))) { + return (result[1][0] !== 'not'); } + return (result[1][0] === 'not'); } + debug(' -- failed within '); + return null; } @@ -398,65 +500,83 @@ function pluralRuleParser(rule, number) { // and_condition = relation ('and' relation)* function and() { - var result = sequence([relation, nOrMore(0, andTail)]); + var i, + result = sequence([relation, nOrMore(0, andTail)]); + if (result) { if (!result[0]) { return false; } - for (var i = 0; i < result[1].length; i++) { + + for (i = 0; i < result[1].length; i++) { if (!result[1][i]) { return false; } } + return true; } + debug(' -- failed and'); + return null; } // ('and' relation)* function andTail() { var result = sequence([whitespace, _and_, whitespace, relation]); + if (result !== null) { debug(' -- passed andTail' + result); + return result[3]; } + debug(' -- failed andTail'); + return null; } // ('or' and_condition)* function orTail() { var result = sequence([whitespace, _or_, whitespace, and]); + if (result !== null) { debug(' -- passed orTail: ' + result[3]); + return result[3]; } + debug(' -- failed orTail'); - return null; + return null; } // condition = and_condition ('or' and_condition)* function condition() { - var result = sequence([and, nOrMore(0, orTail)]); + var i, + result = sequence([and, nOrMore(0, orTail)]); + if (result) { - for (var i = 0; i < result[1].length; i++) { + for (i = 0; i < result[1].length; i++) { if (result[1][i]) { return true; } } - return result[0]; + return result[0]; } + return false; } result = condition(); - /* + + /** * For success, the pos must have gotten to the end of the rule * and returned a non-null. - * n.b. This is part of language infrastructure, so we do not throw an internationalizable message. + * n.b. This is part of language infrastructure, + * so we do not throw an internationalizable message. */ if (result === null) { throw new Error('Parse error at position ' + pos.toString() + ' for rule: ' + rule); |