[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/resources/src/mediawiki.libs/ -> CLDRPluralRuleParser.js (source)

   1  /* This is CLDRPluralRuleParser v1.1, ported to MediaWiki ResourceLoader */
   2  
   3  /**
   4  * CLDRPluralRuleParser.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 <[email protected]>
  12  * @author Timo Tijhof
  13  * @author Amir Aharoni
  14  */
  15  
  16  ( function ( mw ) {
  17  /**
  18   * Evaluates a plural rule in CLDR syntax for a number
  19   * @param {string} rule
  20   * @param {integer} number
  21   * @return {boolean} true if evaluation passed, false if evaluation failed.
  22   */
  23  
  24  function pluralRuleParser(rule, number) {
  25      /*
  26      Syntax: see http://unicode.org/reports/tr35/#Language_Plural_Rules
  27      -----------------------------------------------------------------
  28      condition     = and_condition ('or' and_condition)*
  29          ('@integer' samples)?
  30          ('@decimal' samples)?
  31      and_condition = relation ('and' relation)*
  32      relation      = is_relation | in_relation | within_relation
  33      is_relation   = expr 'is' ('not')? value
  34      in_relation   = expr (('not')? 'in' | '=' | '!=') range_list
  35      within_relation = expr ('not')? 'within' range_list
  36      expr          = operand (('mod' | '%') value)?
  37      operand       = 'n' | 'i' | 'f' | 't' | 'v' | 'w'
  38      range_list    = (range | value) (',' range_list)*
  39      value         = digit+
  40      digit         = 0|1|2|3|4|5|6|7|8|9
  41      range         = value'..'value
  42      samples       = sampleRange (',' sampleRange)* (',' ('…'|'...'))?
  43      sampleRange   = decimalValue '~' decimalValue
  44      decimalValue  = value ('.' value)?
  45      */
  46  
  47      // we don't evaluate the samples section of the rule. Ignore it.
  48      rule = rule.split('@')[0].replace(/^\s*/, '').replace(/\s*$/, '');
  49  
  50      if (!rule.length) {
  51          // empty rule or 'other' rule.
  52          return true;
  53      }
  54      // Indicates current position in the rule as we parse through it.
  55      // Shared among all parsing functions below.
  56      var pos = 0,
  57          operand,
  58          expression,
  59          relation,
  60          result,
  61          whitespace = makeRegexParser(/^\s+/),
  62          value = makeRegexParser(/^\d+/),
  63          _n_ = makeStringParser('n'),
  64          _i_ = makeStringParser('i'),
  65          _f_ = makeStringParser('f'),
  66          _t_ = makeStringParser('t'),
  67          _v_ = makeStringParser('v'),
  68          _w_ = makeStringParser('w'),
  69          _is_ = makeStringParser('is'),
  70          _isnot_ = makeStringParser('is not'),
  71          _isnot_sign_ = makeStringParser('!='),
  72          _equal_ = makeStringParser('='),
  73          _mod_ = makeStringParser('mod'),
  74          _percent_ = makeStringParser('%'),
  75          _not_ = makeStringParser('not'),
  76          _in_ = makeStringParser('in'),
  77          _within_ = makeStringParser('within'),
  78          _range_ = makeStringParser('..'),
  79          _comma_ = makeStringParser(','),
  80          _or_ = makeStringParser('or'),
  81          _and_ = makeStringParser('and');
  82  
  83  	function debug() {
  84          // console.log.apply(console, arguments);
  85      }
  86  
  87      debug('pluralRuleParser', rule, number);
  88  
  89      // Try parsers until one works, if none work return null
  90  
  91  	function choice(parserSyntax) {
  92          return function() {
  93              for (var i = 0; i < parserSyntax.length; i++) {
  94                  var result = parserSyntax[i]();
  95                  if (result !== null) {
  96                      return result;
  97                  }
  98              }
  99              return null;
 100          };
 101      }
 102  
 103      // Try several parserSyntax-es in a row.
 104      // All must succeed; otherwise, return null.
 105      // This is the only eager one.
 106  
 107  	function sequence(parserSyntax) {
 108          var originalPos = pos;
 109          var result = [];
 110          for (var i = 0; i < parserSyntax.length; i++) {
 111              var res = parserSyntax[i]();
 112              if (res === null) {
 113                  pos = originalPos;
 114                  return null;
 115              }
 116              result.push(res);
 117          }
 118          return result;
 119      }
 120  
 121      // Run the same parser over and over until it fails.
 122      // Must succeed a minimum of n times; otherwise, return null.
 123  
 124  	function nOrMore(n, p) {
 125          return function() {
 126              var originalPos = pos;
 127              var result = [];
 128              var parsed = p();
 129              while (parsed !== null) {
 130                  result.push(parsed);
 131                  parsed = p();
 132              }
 133              if (result.length < n) {
 134                  pos = originalPos;
 135                  return null;
 136              }
 137              return result;
 138          };
 139      }
 140  
 141      // Helpers -- just make parserSyntax out of simpler JS builtin types
 142  	function makeStringParser(s) {
 143          var len = s.length;
 144          return function() {
 145              var result = null;
 146              if (rule.substr(pos, len) === s) {
 147                  result = s;
 148                  pos += len;
 149              }
 150  
 151              return result;
 152          };
 153      }
 154  
 155  	function makeRegexParser(regex) {
 156          return function() {
 157              var matches = rule.substr(pos).match(regex);
 158              if (matches === null) {
 159                  return null;
 160              }
 161              pos += matches[0].length;
 162              return matches[0];
 163          };
 164      }
 165  
 166      /*
 167       * integer digits of n.
 168       */
 169      function i() {
 170          var result = _i_();
 171          if (result === null) {
 172              debug(' -- failed i', parseInt(number, 10));
 173              return result;
 174          }
 175          result = parseInt(number, 10);
 176          debug(' -- passed i ', result);
 177          return result;
 178      }
 179  
 180      /*
 181       * absolute value of the source number (integer and decimals).
 182       */
 183      function n() {
 184          var result = _n_();
 185          if (result === null) {
 186              debug(' -- failed n ', number);
 187              return result;
 188          }
 189          result = parseFloat(number, 10);
 190          debug(' -- passed n ', result);
 191          return result;
 192      }
 193  
 194      /*
 195       * visible fractional digits in n, with trailing zeros.
 196       */
 197      function f() {
 198          var result = _f_();
 199          if (result === null) {
 200              debug(' -- failed f ', number);
 201              return result;
 202          }
 203          result = (number + '.').split('.')[1] || 0;
 204          debug(' -- passed f ', result);
 205          return result;
 206      }
 207  
 208      /*
 209       * visible fractional digits in n, without trailing zeros.
 210       */
 211      function t() {
 212          var result = _t_();
 213          if (result === null) {
 214              debug(' -- failed t ', number);
 215              return result;
 216          }
 217          result = (number + '.').split('.')[1].replace(/0$/, '') || 0;
 218          debug(' -- passed t ', result);
 219          return result;
 220      }
 221  
 222      /*
 223       * number of visible fraction digits in n, with trailing zeros.
 224       */
 225      function v() {
 226          var result = _v_();
 227          if (result === null) {
 228              debug(' -- failed v ', number);
 229              return result;
 230          }
 231          result = (number + '.').split('.')[1].length || 0;
 232          debug(' -- passed v ', result);
 233          return result;
 234      }
 235  
 236      /*
 237       * number of visible fraction digits in n, without trailing zeros.
 238       */
 239      function w() {
 240          var result = _w_();
 241          if (result === null) {
 242              debug(' -- failed w ', number);
 243              return result;
 244          }
 245          result = (number + '.').split('.')[1].replace(/0$/, '').length || 0;
 246          debug(' -- passed w ', result);
 247          return result;
 248      }
 249  
 250      // operand       = 'n' | 'i' | 'f' | 't' | 'v' | 'w'
 251      operand = choice([n, i, f, t, v, w]);
 252  
 253      // expr          = operand (('mod' | '%') value)?
 254      expression = choice([mod, operand]);
 255  
 256  	function mod() {
 257          var result = sequence([operand, whitespace, choice([_mod_, _percent_]), whitespace, value]);
 258          if (result === null) {
 259              debug(' -- failed mod');
 260              return null;
 261          }
 262          debug(' -- passed ' + parseInt(result[0], 10) + ' ' + result[2] + ' ' + parseInt(result[4], 10));
 263          return parseInt(result[0], 10) % parseInt(result[4], 10);
 264      }
 265  
 266  	function not() {
 267          var result = sequence([whitespace, _not_]);
 268          if (result === null) {
 269              debug(' -- failed not');
 270              return null;
 271          }
 272  
 273          return result[1];
 274      }
 275  
 276      // is_relation   = expr 'is' ('not')? value
 277      function is() {
 278          var result = sequence([expression, whitespace, choice([_is_]), whitespace, value]);
 279          if (result !== null) {
 280              debug(' -- passed is : ' + result[0] + ' == ' + parseInt(result[4], 10));
 281              return result[0] === parseInt(result[4], 10);
 282          }
 283          debug(' -- failed is');
 284          return null;
 285      }
 286  
 287      // is_relation   = expr 'is' ('not')? value
 288  	function isnot() {
 289          var result = sequence([expression, whitespace, choice([_isnot_, _isnot_sign_]), whitespace, value]);
 290          if (result !== null) {
 291              debug(' -- passed isnot: ' + result[0] + ' != ' + parseInt(result[4], 10));
 292              return result[0] !== parseInt(result[4], 10);
 293          }
 294          debug(' -- failed isnot');
 295          return null;
 296      }
 297  
 298  	function not_in() {
 299          var result = sequence([expression, whitespace, _isnot_sign_, whitespace, rangeList]);
 300          if (result !== null) {
 301              debug(' -- passed not_in: ' + result[0] + ' != ' + result[4]);
 302              var range_list = result[4];
 303              for (var i = 0; i < range_list.length; i++) {
 304                  if (parseInt(range_list[i], 10) === parseInt(result[0], 10)) {
 305                      return false;
 306                  }
 307              }
 308              return true;
 309          }
 310          debug(' -- failed not_in');
 311          return null;
 312      }
 313  
 314      // range_list    = (range | value) (',' range_list)*
 315  	function rangeList() {
 316          var result = sequence([choice([range, value]), nOrMore(0, rangeTail)]);
 317          var resultList = [];
 318          if (result !== null) {
 319              resultList = resultList.concat(result[0]);
 320              if (result[1][0]) {
 321                  resultList = resultList.concat(result[1][0]);
 322              }
 323              return resultList;
 324          }
 325          debug(' -- failed rangeList');
 326          return null;
 327      }
 328  
 329  	function rangeTail() {
 330          // ',' range_list
 331          var result = sequence([_comma_, rangeList]);
 332          if (result !== null) {
 333              return result[1];
 334          }
 335          debug(' -- failed rangeTail');
 336          return null;
 337      }
 338  
 339      // range         = value'..'value
 340  
 341  	function range() {
 342          var i;
 343          var result = sequence([value, _range_, value]);
 344          if (result !== null) {
 345              debug(' -- passed range');
 346              var array = [];
 347              var left = parseInt(result[0], 10);
 348              var right = parseInt(result[2], 10);
 349              for (i = left; i <= right; i++) {
 350                  array.push(i);
 351              }
 352              return array;
 353          }
 354          debug(' -- failed range');
 355          return null;
 356      }
 357  
 358  	function _in() {
 359          // in_relation   = expr ('not')? 'in' range_list
 360          var result = sequence([expression, nOrMore(0, not), whitespace, choice([_in_, _equal_]), whitespace, rangeList]);
 361          if (result !== null) {
 362              debug(' -- passed _in:' + result);
 363              var range_list = result[5];
 364              for (var i = 0; i < range_list.length; i++) {
 365                  if (parseInt(range_list[i], 10) === parseInt(result[0], 10)) {
 366                      return (result[1][0] !== 'not');
 367                  }
 368              }
 369              return (result[1][0] === 'not');
 370          }
 371          debug(' -- failed _in ');
 372          return null;
 373      }
 374  
 375      /*
 376       * The difference between in and within is that in only includes integers in the specified range,
 377       * while within includes all values.
 378       */
 379  
 380  	function within() {
 381          // within_relation = expr ('not')? 'within' range_list
 382          var result = sequence([expression, nOrMore(0, not), whitespace, _within_, whitespace, rangeList]);
 383          if (result !== null) {
 384              debug(' -- passed within');
 385              var range_list = result[5];
 386              if ((result[0] >= parseInt(range_list[0], 10)) &&
 387                  (result[0] < parseInt(range_list[range_list.length - 1], 10))) {
 388                  return (result[1][0] !== 'not');
 389              }
 390              return (result[1][0] === 'not');
 391          }
 392          debug(' -- failed within ');
 393          return null;
 394      }
 395  
 396      // relation      = is_relation | in_relation | within_relation
 397      relation = choice([is, not_in, isnot, _in, within]);
 398  
 399      // and_condition = relation ('and' relation)*
 400  	function and() {
 401          var result = sequence([relation, nOrMore(0, andTail)]);
 402          if (result) {
 403              if (!result[0]) {
 404                  return false;
 405              }
 406              for (var i = 0; i < result[1].length; i++) {
 407                  if (!result[1][i]) {
 408                      return false;
 409                  }
 410              }
 411              return true;
 412          }
 413          debug(' -- failed and');
 414          return null;
 415      }
 416  
 417      // ('and' relation)*
 418  	function andTail() {
 419          var result = sequence([whitespace, _and_, whitespace, relation]);
 420          if (result !== null) {
 421              debug(' -- passed andTail' + result);
 422              return result[3];
 423          }
 424          debug(' -- failed andTail');
 425          return null;
 426  
 427      }
 428      //  ('or' and_condition)*
 429  	function orTail() {
 430          var result = sequence([whitespace, _or_, whitespace, and]);
 431          if (result !== null) {
 432              debug(' -- passed orTail: ' + result[3]);
 433              return result[3];
 434          }
 435          debug(' -- failed orTail');
 436          return null;
 437  
 438      }
 439  
 440      // condition     = and_condition ('or' and_condition)*
 441  	function condition() {
 442          var result = sequence([and, nOrMore(0, orTail)]);
 443          if (result) {
 444              for (var i = 0; i < result[1].length; i++) {
 445                  if (result[1][i]) {
 446                      return true;
 447                  }
 448              }
 449              return result[0];
 450  
 451          }
 452          return false;
 453      }
 454  
 455      result = condition();
 456      /*
 457       * For success, the pos must have gotten to the end of the rule
 458       * and returned a non-null.
 459       * n.b. This is part of language infrastructure, so we do not throw an internationalizable message.
 460       */
 461      if (result === null) {
 462          throw new Error('Parse error at position ' + pos.toString() + ' for rule: ' + rule);
 463      }
 464  
 465      if (pos !== rule.length) {
 466          debug('Warning: Rule not parsed completely. Parser stopped at ' + rule.substr(0, pos) + ' for rule: ' + rule);
 467      }
 468  
 469      return result;
 470  }
 471  
 472  /* pluralRuleParser ends here */
 473  mw.libs.pluralRuleParser = pluralRuleParser;
 474  
 475  } )( mediaWiki );


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1