diff options
Diffstat (limited to 'resources/mediawiki.libs')
-rw-r--r-- | resources/mediawiki.libs/CLDRPluralRuleParser.js | 314 |
1 files changed, 314 insertions, 0 deletions
diff --git a/resources/mediawiki.libs/CLDRPluralRuleParser.js b/resources/mediawiki.libs/CLDRPluralRuleParser.js new file mode 100644 index 00000000..91bdc07d --- /dev/null +++ b/resources/mediawiki.libs/CLDRPluralRuleParser.js @@ -0,0 +1,314 @@ +/* This is cldrpluralparser 1.0, ported to MediaWiki ResourceLoader */ + +/** +* cldrpluralparser.js +* A parser engine for CLDR plural rules. +* +* Copyright 2012 GPLV3+, Santhosh Thottingal +* +* @version 0.1.0-alpha +* @source https://github.com/santhoshtr/CLDRPluralRuleParser +* @author Santhosh Thottingal <santhosh.thottingal@gmail.com> +* @author Timo Tijhof +* @author Amir Aharoni +*/ + +/** + * Evaluates a plural rule in CLDR syntax for a number + * @param rule + * @param number + * @return true|false|null + */ +( function( mw ) { + +function pluralRuleParser(rule, number) { + /* + Syntax: see http://unicode.org/reports/tr35/#Language_Plural_Rules + ----------------------------------------------------------------- + + condition = and_condition ('or' and_condition)* + and_condition = relation ('and' relation)* + relation = is_relation | in_relation | within_relation | 'n' <EOL> + is_relation = expr 'is' ('not')? value + in_relation = expr ('not')? 'in' range_list + within_relation = expr ('not')? 'within' range_list + expr = 'n' ('mod' value)? + range_list = (range | value) (',' range_list)* + value = digit+ + digit = 0|1|2|3|4|5|6|7|8|9 + range = value'..'value + + */ + // Indicates current position in the rule as we parse through it. + // Shared among all parsing functions below. + var pos = 0; + + var whitespace = makeRegexParser(/^\s+/); + var digits = makeRegexParser(/^\d+/); + + var _n_ = makeStringParser('n'); + var _is_ = makeStringParser('is'); + var _mod_ = makeStringParser('mod'); + var _not_ = makeStringParser('not'); + var _in_ = makeStringParser('in'); + var _within_ = makeStringParser('within'); + var _range_ = makeStringParser('..'); + var _comma_ = makeStringParser(','); + var _or_ = makeStringParser('or'); + var _and_ = makeStringParser('and'); + + function debug() { + /* console.log.apply(console, arguments);*/ + } + + 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](); + if (result !== null) { + return result; + } + } + return null; + }; + } + + // 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) { + pos = originalPos; + return null; + } + result.push(res); + } + 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(); + 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 + + function makeStringParser(s) { + var len = s.length; + return function () { + var result = null; + if (rule.substr(pos, len) === s) { + result = s; + pos += len; + } + return result; + }; + } + + function makeRegexParser(regex) { + return function () { + var matches = rule.substr(pos).match(regex); + if (matches === null) { + return null; + } + pos += matches[0].length; + return matches[0]; + }; + } + + function n() { + var result = _n_(); + if (result === null) { + debug(" -- failed n"); + return result; + } + result = parseInt(number, 10); + debug(" -- passed n ", result); + return result; + } + + var expression = choice([mod, n]); + + function mod() { + var result = sequence([n, whitespace, _mod_, whitespace, digits]); + if (result === null) { + debug(" -- failed mod"); + return null; + } + debug(" -- passed mod"); + return parseInt(result[0], 10) % parseInt(result[4], 10); + } + + function not() { + var result = sequence([whitespace, _not_]); + if (result === null) { + debug(" -- failed not"); + return null; + } else { + return result[1]; + } + } + + function is() { + var result = sequence([expression, whitespace, _is_, nOrMore(0, not), whitespace, digits]); + if (result !== null) { + debug(" -- passed is"); + if (result[3][0] === 'not') { + return result[0] !== parseInt(result[5], 10); + } else { + return result[0] === parseInt(result[5], 10); + } + } + debug(" -- failed is"); + return null; + } + + function rangeList() { + // range_list = (range | value) (',' range_list)* + var result = sequence([choice([range, digits]), nOrMore(0, rangeTail)]); + var resultList = []; + if (result !== null) { + resultList = resultList.concat(result[0], 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; + } + + function range() { + var result = sequence([digits, _range_, digits]); + if (result !== null) { + debug(" -- passed range"); + var array = []; + var left = parseInt(result[0], 10); + var right = parseInt(result[2], 10); + for ( i = left; i <= right; i++) { + array.push(i); + } + return array; + } + debug(" -- failed range"); + return null; + } + + function _in() { + // in_relation = expr ('not')? 'in' range_list + var result = sequence([expression, nOrMore(0, not), whitespace, _in_, whitespace, rangeList]); + if (result !== null) { + debug(" -- passed _in"); + var range_list = result[5]; + for (var i = 0; i < range_list.length; i++) { + if (parseInt(range_list[i], 10) === result[0]) { + return (result[1][0] !== 'not'); + } + } + return (result[1][0] === 'not'); + } + debug(" -- failed _in "); + return null; + } + + function within() { + var result = sequence([expression, whitespace, _within_, whitespace, rangeList]); + if (result !== null) { + debug(" -- passed within "); + var range_list = result[4]; + return (parseInt( range_list[0],10 )<= result[0] && result[0] <= parseInt( range_list[1], 10)); + } + debug(" -- failed within "); + return null; + } + + + var relation = choice([is, _in, within]); + + function and() { + var result = sequence([relation, whitespace, _and_, whitespace, condition]); + if (result) { + debug(" -- passed and"); + return result[0] && result[4]; + } + debug(" -- failed and"); + return null; + } + + function or() { + var result = sequence([relation, whitespace, _or_, whitespace, condition]); + if (result) { + debug(" -- passed or"); + return result[0] || result[4]; + } + debug(" -- failed or"); + return null; + } + + var condition = choice([and, or, relation]); + + function isInt(n) { + return parseFloat(n) % 1 === 0; + } + + + function start() { + if (!isInt(number)) { + return false; + } + var result = condition(); + return result; + } + + + var result = start(); + + /* + * 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. + */ + if (result === null || pos !== rule.length) { + // throw new Error("Parse error at position " + pos.toString() + " in input: " + rule + " result is " + result); + } + + return result; +} + +/* For module loaders, e.g. NodeJS, NPM */ +if (typeof module !== 'undefined' && module.exports) { + module.exports = pluralRuleParser; +} + +/* pluralRuleParser ends here */ +mw.libs.pluralRuleParser = pluralRuleParser; + +} )( mediaWiki ); |