jed.js 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008
  1. /*
  2. jed.js
  3. v0.5.0beta
  4. https://github.com/SlexAxton/Jed
  5. -----------
  6. A gettext compatible i18n library for modern JavaScript Applications
  7. by Alex Sexton - AlexSexton [at] gmail - @SlexAxton
  8. WTFPL license for use
  9. Dojo CLA for contributions
  10. Jed offers the entire applicable GNU gettext spec'd set of
  11. functions, but also offers some nicer wrappers around them.
  12. The api for gettext was written for a language with no function
  13. overloading, so Jed allows a little more of that.
  14. Many thanks to Joshua I. Miller - unrtst@cpan.org - who wrote
  15. gettext.js back in 2008. I was able to vet a lot of my ideas
  16. against his. I also made sure Jed passed against his tests
  17. in order to offer easy upgrades -- jsgettext.berlios.de
  18. */
  19. (function (root, undef) {
  20. // Set up some underscore-style functions, if you already have
  21. // underscore, feel free to delete this section, and use it
  22. // directly, however, the amount of functions used doesn't
  23. // warrant having underscore as a full dependency.
  24. // Underscore 1.3.0 was used to port and is licensed
  25. // under the MIT License by Jeremy Ashkenas.
  26. var ArrayProto = Array.prototype,
  27. ObjProto = Object.prototype,
  28. slice = ArrayProto.slice,
  29. hasOwnProp = ObjProto.hasOwnProperty,
  30. nativeForEach = ArrayProto.forEach,
  31. breaker = {};
  32. // We're not using the OOP style _ so we don't need the
  33. // extra level of indirection. This still means that you
  34. // sub out for real `_` though.
  35. var _ = {
  36. forEach : function( obj, iterator, context ) {
  37. var i, l, key;
  38. if ( obj === null ) {
  39. return;
  40. }
  41. if ( nativeForEach && obj.forEach === nativeForEach ) {
  42. obj.forEach( iterator, context );
  43. }
  44. else if ( obj.length === +obj.length ) {
  45. for ( i = 0, l = obj.length; i < l; i++ ) {
  46. if ( i in obj && iterator.call( context, obj[i], i, obj ) === breaker ) {
  47. return;
  48. }
  49. }
  50. }
  51. else {
  52. for ( key in obj) {
  53. if ( hasOwnProp.call( obj, key ) ) {
  54. if ( iterator.call (context, obj[key], key, obj ) === breaker ) {
  55. return;
  56. }
  57. }
  58. }
  59. }
  60. },
  61. extend : function( obj ) {
  62. this.forEach( slice.call( arguments, 1 ), function ( source ) {
  63. for ( var prop in source ) {
  64. obj[prop] = source[prop];
  65. }
  66. });
  67. return obj;
  68. }
  69. };
  70. // END Miniature underscore impl
  71. // Jed is a constructor function
  72. var Jed = function ( options ) {
  73. // Some minimal defaults
  74. this.defaults = {
  75. "locale_data" : {
  76. "messages" : {
  77. "" : {
  78. "domain" : "messages",
  79. "lang" : "en",
  80. "plural_forms" : "nplurals=2; plural=(n != 1);"
  81. }
  82. // There are no default keys, though
  83. }
  84. },
  85. // The default domain if one is missing
  86. "domain" : "messages"
  87. };
  88. // Mix in the sent options with the default options
  89. this.options = _.extend( {}, this.defaults, options );
  90. this.textdomain( this.options.domain );
  91. if ( options.domain && ! this.options.locale_data[ this.options.domain ] ) {
  92. throw new Error('Text domain set to non-existent domain: `' + options.domain + '`');
  93. }
  94. };
  95. // The gettext spec sets this character as the default
  96. // delimiter for context lookups.
  97. // e.g.: context\u0004key
  98. // If your translation company uses something different,
  99. // just change this at any time and it will use that instead.
  100. Jed.context_delimiter = String.fromCharCode( 4 );
  101. function getPluralFormFunc ( plural_form_string ) {
  102. return Jed.PF.compile( plural_form_string || "nplurals=2; plural=(n != 1);");
  103. }
  104. function Chain( key, i18n ){
  105. this._key = key;
  106. this._i18n = i18n;
  107. }
  108. // Create a chainable api for adding args prettily
  109. _.extend( Chain.prototype, {
  110. onDomain : function ( domain ) {
  111. this._domain = domain;
  112. return this;
  113. },
  114. withContext : function ( context ) {
  115. this._context = context;
  116. return this;
  117. },
  118. ifPlural : function ( num, pkey ) {
  119. this._val = num;
  120. this._pkey = pkey;
  121. return this;
  122. },
  123. fetch : function ( sArr ) {
  124. if ( {}.toString.call( sArr ) != '[object Array]' ) {
  125. sArr = [].slice.call(arguments);
  126. }
  127. return ( sArr && sArr.length ? Jed.sprintf : function(x){ return x; } )(
  128. this._i18n.dcnpgettext(this._domain, this._context, this._key, this._pkey, this._val),
  129. sArr
  130. );
  131. }
  132. });
  133. // Add functions to the Jed prototype.
  134. // These will be the functions on the object that's returned
  135. // from creating a `new Jed()`
  136. // These seem redundant, but they gzip pretty well.
  137. _.extend( Jed.prototype, {
  138. // The sexier api start point
  139. translate : function ( key ) {
  140. return new Chain( key, this );
  141. },
  142. textdomain : function ( domain ) {
  143. if ( ! domain ) {
  144. return this._textdomain;
  145. }
  146. this._textdomain = domain;
  147. },
  148. gettext : function ( key ) {
  149. return this.dcnpgettext.call( this, undef, undef, key );
  150. },
  151. dgettext : function ( domain, key ) {
  152. return this.dcnpgettext.call( this, domain, undef, key );
  153. },
  154. dcgettext : function ( domain , key /*, category */ ) {
  155. // Ignores the category anyways
  156. return this.dcnpgettext.call( this, domain, undef, key );
  157. },
  158. ngettext : function ( skey, pkey, val ) {
  159. return this.dcnpgettext.call( this, undef, undef, skey, pkey, val );
  160. },
  161. dngettext : function ( domain, skey, pkey, val ) {
  162. return this.dcnpgettext.call( this, domain, undef, skey, pkey, val );
  163. },
  164. dcngettext : function ( domain, skey, pkey, val/*, category */) {
  165. return this.dcnpgettext.call( this, domain, undef, skey, pkey, val );
  166. },
  167. pgettext : function ( context, key ) {
  168. return this.dcnpgettext.call( this, undef, context, key );
  169. },
  170. dpgettext : function ( domain, context, key ) {
  171. return this.dcnpgettext.call( this, domain, context, key );
  172. },
  173. dcpgettext : function ( domain, context, key/*, category */) {
  174. return this.dcnpgettext.call( this, domain, context, key );
  175. },
  176. npgettext : function ( context, skey, pkey, val ) {
  177. return this.dcnpgettext.call( this, undef, context, skey, pkey, val );
  178. },
  179. dnpgettext : function ( domain, context, skey, pkey, val ) {
  180. return this.dcnpgettext.call( this, domain, context, skey, pkey, val );
  181. },
  182. // The most fully qualified gettext function. It has every option.
  183. // Since it has every option, we can use it from every other method.
  184. // This is the bread and butter.
  185. // Technically there should be one more argument in this function for 'Category',
  186. // but since we never use it, we might as well not waste the bytes to define it.
  187. dcnpgettext : function ( domain, context, singular_key, plural_key, val ) {
  188. // Set some defaults
  189. plural_key = plural_key || singular_key;
  190. // Use the global domain default if one
  191. // isn't explicitly passed in
  192. domain = domain || this._textdomain;
  193. // Default the value to the singular case
  194. val = typeof val == 'undefined' ? 1 : val;
  195. var fallback;
  196. // Handle special cases
  197. // No options found
  198. if ( ! this.options ) {
  199. // There's likely something wrong, but we'll return the correct key for english
  200. // We do this by instantiating a brand new Jed instance with the default set
  201. // for everything that could be broken.
  202. fallback = new Jed();
  203. return fallback.dcnpgettext.call( fallback, undefined, undefined, singular_key, plural_key, val );
  204. }
  205. // No translation data provided
  206. if ( ! this.options.locale_data ) {
  207. throw new Error('No locale data provided.');
  208. }
  209. if ( ! this.options.locale_data[ domain ] ) {
  210. throw new Error('Domain `' + domain + '` was not found.');
  211. }
  212. if ( ! this.options.locale_data[ domain ][ "" ] ) {
  213. throw new Error('No locale meta information provided.');
  214. }
  215. // Make sure we have a truthy key. Otherwise we might start looking
  216. // into the empty string key, which is the options for the locale
  217. // data.
  218. if ( ! singular_key ) {
  219. throw new Error('No translation key found.');
  220. }
  221. // Handle invalid numbers, but try casting strings for good measure
  222. if ( typeof val != 'number' ) {
  223. val = parseInt( val, 10 );
  224. if ( isNaN( val ) ) {
  225. throw new Error('The number that was passed in is not a number.');
  226. }
  227. }
  228. var key = context ? context + Jed.context_delimiter + singular_key : singular_key,
  229. locale_data = this.options.locale_data,
  230. dict = locale_data[ domain ],
  231. pluralForms = dict[""].plural_forms || (locale_data.messages || this.defaults.locale_data.messages)[""].plural_forms,
  232. val_idx = getPluralFormFunc(pluralForms)(val) + 1,
  233. val_list,
  234. res;
  235. // Throw an error if a domain isn't found
  236. if ( ! dict ) {
  237. throw new Error('No domain named `' + domain + '` could be found.');
  238. }
  239. val_list = dict[ key ];
  240. // If there is no match, then revert back to
  241. // english style singular/plural with the keys passed in.
  242. if ( ! val_list || val_idx >= val_list.length ) {
  243. if (this.options.missing_key_callback) {
  244. this.options.missing_key_callback(key);
  245. }
  246. res = [ null, singular_key, plural_key ];
  247. return res[ getPluralFormFunc(pluralForms)( val ) + 1 ];
  248. }
  249. res = val_list[ val_idx ];
  250. // This includes empty strings on purpose
  251. if ( ! res ) {
  252. res = [ null, singular_key, plural_key ];
  253. return res[ getPluralFormFunc(pluralForms)( val ) + 1 ];
  254. }
  255. return res;
  256. }
  257. });
  258. // We add in sprintf capabilities for post translation value interolation
  259. // This is not internally used, so you can remove it if you have this
  260. // available somewhere else, or want to use a different system.
  261. // We _slightly_ modify the normal sprintf behavior to more gracefully handle
  262. // undefined values.
  263. /**
  264. sprintf() for JavaScript 0.7-beta1
  265. http://www.diveintojavascript.com/projects/javascript-sprintf
  266. Copyright (c) Alexandru Marasteanu <alexaholic [at) gmail (dot] com>
  267. All rights reserved.
  268. Redistribution and use in source and binary forms, with or without
  269. modification, are permitted provided that the following conditions are met:
  270. * Redistributions of source code must retain the above copyright
  271. notice, this list of conditions and the following disclaimer.
  272. * Redistributions in binary form must reproduce the above copyright
  273. notice, this list of conditions and the following disclaimer in the
  274. documentation and/or other materials provided with the distribution.
  275. * Neither the name of sprintf() for JavaScript nor the
  276. names of its contributors may be used to endorse or promote products
  277. derived from this software without specific prior written permission.
  278. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  279. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  280. WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  281. DISCLAIMED. IN NO EVENT SHALL Alexandru Marasteanu BE LIABLE FOR ANY
  282. DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  283. (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  284. LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  285. ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  286. (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  287. SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  288. */
  289. var sprintf = (function() {
  290. function get_type(variable) {
  291. return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
  292. }
  293. function str_repeat(input, multiplier) {
  294. for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */}
  295. return output.join('');
  296. }
  297. var str_format = function() {
  298. if (!str_format.cache.hasOwnProperty(arguments[0])) {
  299. str_format.cache[arguments[0]] = str_format.parse(arguments[0]);
  300. }
  301. return str_format.format.call(null, str_format.cache[arguments[0]], arguments);
  302. };
  303. str_format.format = function(parse_tree, argv) {
  304. var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
  305. for (i = 0; i < tree_length; i++) {
  306. node_type = get_type(parse_tree[i]);
  307. if (node_type === 'string') {
  308. output.push(parse_tree[i]);
  309. }
  310. else if (node_type === 'array') {
  311. match = parse_tree[i]; // convenience purposes only
  312. if (match[2]) { // keyword argument
  313. arg = argv[cursor];
  314. for (k = 0; k < match[2].length; k++) {
  315. if (!arg.hasOwnProperty(match[2][k])) {
  316. throw(sprintf('[sprintf] property "%s" does not exist', match[2][k]));
  317. }
  318. arg = arg[match[2][k]];
  319. }
  320. }
  321. else if (match[1]) { // positional argument (explicit)
  322. arg = argv[match[1]];
  323. }
  324. else { // positional argument (implicit)
  325. arg = argv[cursor++];
  326. }
  327. if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) {
  328. throw(sprintf('[sprintf] expecting number but found %s', get_type(arg)));
  329. }
  330. // Jed EDIT
  331. if ( typeof arg == 'undefined' || arg === null ) {
  332. arg = '';
  333. }
  334. // Jed EDIT
  335. switch (match[8]) {
  336. case 'b': arg = arg.toString(2); break;
  337. case 'c': arg = String.fromCharCode(arg); break;
  338. case 'd': arg = parseInt(arg, 10); break;
  339. case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
  340. case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
  341. case 'o': arg = arg.toString(8); break;
  342. case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
  343. case 'u': arg = Math.abs(arg); break;
  344. case 'x': arg = arg.toString(16); break;
  345. case 'X': arg = arg.toString(16).toUpperCase(); break;
  346. }
  347. arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
  348. pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
  349. pad_length = match[6] - String(arg).length;
  350. pad = match[6] ? str_repeat(pad_character, pad_length) : '';
  351. output.push(match[5] ? arg + pad : pad + arg);
  352. }
  353. }
  354. return output.join('');
  355. };
  356. str_format.cache = {};
  357. str_format.parse = function(fmt) {
  358. var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
  359. while (_fmt) {
  360. if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
  361. parse_tree.push(match[0]);
  362. }
  363. else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
  364. parse_tree.push('%');
  365. }
  366. else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
  367. if (match[2]) {
  368. arg_names |= 1;
  369. var field_list = [], replacement_field = match[2], field_match = [];
  370. if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
  371. field_list.push(field_match[1]);
  372. while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
  373. if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
  374. field_list.push(field_match[1]);
  375. }
  376. else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
  377. field_list.push(field_match[1]);
  378. }
  379. else {
  380. throw('[sprintf] huh?');
  381. }
  382. }
  383. }
  384. else {
  385. throw('[sprintf] huh?');
  386. }
  387. match[2] = field_list;
  388. }
  389. else {
  390. arg_names |= 2;
  391. }
  392. if (arg_names === 3) {
  393. throw('[sprintf] mixing positional and named placeholders is not (yet) supported');
  394. }
  395. parse_tree.push(match);
  396. }
  397. else {
  398. throw('[sprintf] huh?');
  399. }
  400. _fmt = _fmt.substring(match[0].length);
  401. }
  402. return parse_tree;
  403. };
  404. return str_format;
  405. })();
  406. var vsprintf = function(fmt, argv) {
  407. argv.unshift(fmt);
  408. return sprintf.apply(null, argv);
  409. };
  410. Jed.parse_plural = function ( plural_forms, n ) {
  411. plural_forms = plural_forms.replace(/n/g, n);
  412. return Jed.parse_expression(plural_forms);
  413. };
  414. Jed.sprintf = function ( fmt, args ) {
  415. if ( {}.toString.call( args ) == '[object Array]' ) {
  416. return vsprintf( fmt, [].slice.call(args) );
  417. }
  418. return sprintf.apply(this, [].slice.call(arguments) );
  419. };
  420. Jed.prototype.sprintf = function () {
  421. return Jed.sprintf.apply(this, arguments);
  422. };
  423. // END sprintf Implementation
  424. // Start the Plural forms section
  425. // This is a full plural form expression parser. It is used to avoid
  426. // running 'eval' or 'new Function' directly against the plural
  427. // forms.
  428. //
  429. // This can be important if you get translations done through a 3rd
  430. // party vendor. I encourage you to use this instead, however, I
  431. // also will provide a 'precompiler' that you can use at build time
  432. // to output valid/safe function representations of the plural form
  433. // expressions. This means you can build this code out for the most
  434. // part.
  435. Jed.PF = {};
  436. Jed.PF.parse = function ( p ) {
  437. var plural_str = Jed.PF.extractPluralExpr( p );
  438. return Jed.PF.parser.parse.call(Jed.PF.parser, plural_str);
  439. };
  440. Jed.PF.compile = function ( p ) {
  441. // Handle trues and falses as 0 and 1
  442. function imply( val ) {
  443. return (val === true ? 1 : val ? val : 0);
  444. }
  445. var ast = Jed.PF.parse( p );
  446. return function ( n ) {
  447. return imply( Jed.PF.interpreter( ast )( n ) );
  448. };
  449. };
  450. Jed.PF.interpreter = function ( ast ) {
  451. return function ( n ) {
  452. var res;
  453. switch ( ast.type ) {
  454. case 'GROUP':
  455. return Jed.PF.interpreter( ast.expr )( n );
  456. case 'TERNARY':
  457. if ( Jed.PF.interpreter( ast.expr )( n ) ) {
  458. return Jed.PF.interpreter( ast.truthy )( n );
  459. }
  460. return Jed.PF.interpreter( ast.falsey )( n );
  461. case 'OR':
  462. return Jed.PF.interpreter( ast.left )( n ) || Jed.PF.interpreter( ast.right )( n );
  463. case 'AND':
  464. return Jed.PF.interpreter( ast.left )( n ) && Jed.PF.interpreter( ast.right )( n );
  465. case 'LT':
  466. return Jed.PF.interpreter( ast.left )( n ) < Jed.PF.interpreter( ast.right )( n );
  467. case 'GT':
  468. return Jed.PF.interpreter( ast.left )( n ) > Jed.PF.interpreter( ast.right )( n );
  469. case 'LTE':
  470. return Jed.PF.interpreter( ast.left )( n ) <= Jed.PF.interpreter( ast.right )( n );
  471. case 'GTE':
  472. return Jed.PF.interpreter( ast.left )( n ) >= Jed.PF.interpreter( ast.right )( n );
  473. case 'EQ':
  474. return Jed.PF.interpreter( ast.left )( n ) == Jed.PF.interpreter( ast.right )( n );
  475. case 'NEQ':
  476. return Jed.PF.interpreter( ast.left )( n ) != Jed.PF.interpreter( ast.right )( n );
  477. case 'MOD':
  478. return Jed.PF.interpreter( ast.left )( n ) % Jed.PF.interpreter( ast.right )( n );
  479. case 'VAR':
  480. return n;
  481. case 'NUM':
  482. return ast.val;
  483. default:
  484. throw new Error("Invalid Token found.");
  485. }
  486. };
  487. };
  488. Jed.PF.extractPluralExpr = function ( p ) {
  489. // trim first
  490. p = p.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
  491. if (! /;\s*$/.test(p)) {
  492. p = p.concat(';');
  493. }
  494. var nplurals_re = /nplurals\=(\d+);/,
  495. plural_re = /plural\=(.*);/,
  496. nplurals_matches = p.match( nplurals_re ),
  497. res = {},
  498. plural_matches;
  499. // Find the nplurals number
  500. if ( nplurals_matches.length > 1 ) {
  501. res.nplurals = nplurals_matches[1];
  502. }
  503. else {
  504. throw new Error('nplurals not found in plural_forms string: ' + p );
  505. }
  506. // remove that data to get to the formula
  507. p = p.replace( nplurals_re, "" );
  508. plural_matches = p.match( plural_re );
  509. if (!( plural_matches && plural_matches.length > 1 ) ) {
  510. throw new Error('`plural` expression not found: ' + p);
  511. }
  512. return plural_matches[ 1 ];
  513. };
  514. /* Jison generated parser */
  515. Jed.PF.parser = (function(){
  516. var parser = {trace: function trace() { },
  517. yy: {},
  518. symbols_: {"error":2,"expressions":3,"e":4,"EOF":5,"?":6,":":7,"||":8,"&&":9,"<":10,"<=":11,">":12,">=":13,"!=":14,"==":15,"%":16,"(":17,")":18,"n":19,"NUMBER":20,"$accept":0,"$end":1},
  519. terminals_: {2:"error",5:"EOF",6:"?",7:":",8:"||",9:"&&",10:"<",11:"<=",12:">",13:">=",14:"!=",15:"==",16:"%",17:"(",18:")",19:"n",20:"NUMBER"},
  520. productions_: [0,[3,2],[4,5],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,1],[4,1]],
  521. performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) {
  522. var $0 = $$.length - 1;
  523. switch (yystate) {
  524. case 1: return { type : 'GROUP', expr: $$[$0-1] };
  525. break;
  526. case 2:this.$ = { type: 'TERNARY', expr: $$[$0-4], truthy : $$[$0-2], falsey: $$[$0] };
  527. break;
  528. case 3:this.$ = { type: "OR", left: $$[$0-2], right: $$[$0] };
  529. break;
  530. case 4:this.$ = { type: "AND", left: $$[$0-2], right: $$[$0] };
  531. break;
  532. case 5:this.$ = { type: 'LT', left: $$[$0-2], right: $$[$0] };
  533. break;
  534. case 6:this.$ = { type: 'LTE', left: $$[$0-2], right: $$[$0] };
  535. break;
  536. case 7:this.$ = { type: 'GT', left: $$[$0-2], right: $$[$0] };
  537. break;
  538. case 8:this.$ = { type: 'GTE', left: $$[$0-2], right: $$[$0] };
  539. break;
  540. case 9:this.$ = { type: 'NEQ', left: $$[$0-2], right: $$[$0] };
  541. break;
  542. case 10:this.$ = { type: 'EQ', left: $$[$0-2], right: $$[$0] };
  543. break;
  544. case 11:this.$ = { type: 'MOD', left: $$[$0-2], right: $$[$0] };
  545. break;
  546. case 12:this.$ = { type: 'GROUP', expr: $$[$0-1] };
  547. break;
  548. case 13:this.$ = { type: 'VAR' };
  549. break;
  550. case 14:this.$ = { type: 'NUM', val: Number(yytext) };
  551. break;
  552. }
  553. },
  554. table: [{3:1,4:2,17:[1,3],19:[1,4],20:[1,5]},{1:[3]},{5:[1,6],6:[1,7],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16]},{4:17,17:[1,3],19:[1,4],20:[1,5]},{5:[2,13],6:[2,13],7:[2,13],8:[2,13],9:[2,13],10:[2,13],11:[2,13],12:[2,13],13:[2,13],14:[2,13],15:[2,13],16:[2,13],18:[2,13]},{5:[2,14],6:[2,14],7:[2,14],8:[2,14],9:[2,14],10:[2,14],11:[2,14],12:[2,14],13:[2,14],14:[2,14],15:[2,14],16:[2,14],18:[2,14]},{1:[2,1]},{4:18,17:[1,3],19:[1,4],20:[1,5]},{4:19,17:[1,3],19:[1,4],20:[1,5]},{4:20,17:[1,3],19:[1,4],20:[1,5]},{4:21,17:[1,3],19:[1,4],20:[1,5]},{4:22,17:[1,3],19:[1,4],20:[1,5]},{4:23,17:[1,3],19:[1,4],20:[1,5]},{4:24,17:[1,3],19:[1,4],20:[1,5]},{4:25,17:[1,3],19:[1,4],20:[1,5]},{4:26,17:[1,3],19:[1,4],20:[1,5]},{4:27,17:[1,3],19:[1,4],20:[1,5]},{6:[1,7],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[1,28]},{6:[1,7],7:[1,29],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16]},{5:[2,3],6:[2,3],7:[2,3],8:[2,3],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[2,3]},{5:[2,4],6:[2,4],7:[2,4],8:[2,4],9:[2,4],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[2,4]},{5:[2,5],6:[2,5],7:[2,5],8:[2,5],9:[2,5],10:[2,5],11:[2,5],12:[2,5],13:[2,5],14:[2,5],15:[2,5],16:[1,16],18:[2,5]},{5:[2,6],6:[2,6],7:[2,6],8:[2,6],9:[2,6],10:[2,6],11:[2,6],12:[2,6],13:[2,6],14:[2,6],15:[2,6],16:[1,16],18:[2,6]},{5:[2,7],6:[2,7],7:[2,7],8:[2,7],9:[2,7],10:[2,7],11:[2,7],12:[2,7],13:[2,7],14:[2,7],15:[2,7],16:[1,16],18:[2,7]},{5:[2,8],6:[2,8],7:[2,8],8:[2,8],9:[2,8],10:[2,8],11:[2,8],12:[2,8],13:[2,8],14:[2,8],15:[2,8],16:[1,16],18:[2,8]},{5:[2,9],6:[2,9],7:[2,9],8:[2,9],9:[2,9],10:[2,9],11:[2,9],12:[2,9],13:[2,9],14:[2,9],15:[2,9],16:[1,16],18:[2,9]},{5:[2,10],6:[2,10],7:[2,10],8:[2,10],9:[2,10],10:[2,10],11:[2,10],12:[2,10],13:[2,10],14:[2,10],15:[2,10],16:[1,16],18:[2,10]},{5:[2,11],6:[2,11],7:[2,11],8:[2,11],9:[2,11],10:[2,11],11:[2,11],12:[2,11],13:[2,11],14:[2,11],15:[2,11],16:[2,11],18:[2,11]},{5:[2,12],6:[2,12],7:[2,12],8:[2,12],9:[2,12],10:[2,12],11:[2,12],12:[2,12],13:[2,12],14:[2,12],15:[2,12],16:[2,12],18:[2,12]},{4:30,17:[1,3],19:[1,4],20:[1,5]},{5:[2,2],6:[1,7],7:[2,2],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[2,2]}],
  555. defaultActions: {6:[2,1]},
  556. parseError: function parseError(str, hash) {
  557. throw new Error(str);
  558. },
  559. parse: function parse(input) {
  560. var self = this,
  561. stack = [0],
  562. vstack = [null], // semantic value stack
  563. lstack = [], // location stack
  564. table = this.table,
  565. yytext = '',
  566. yylineno = 0,
  567. yyleng = 0,
  568. recovering = 0,
  569. TERROR = 2,
  570. EOF = 1;
  571. //this.reductionCount = this.shiftCount = 0;
  572. this.lexer.setInput(input);
  573. this.lexer.yy = this.yy;
  574. this.yy.lexer = this.lexer;
  575. if (typeof this.lexer.yylloc == 'undefined')
  576. this.lexer.yylloc = {};
  577. var yyloc = this.lexer.yylloc;
  578. lstack.push(yyloc);
  579. if (typeof this.yy.parseError === 'function')
  580. this.parseError = this.yy.parseError;
  581. function popStack (n) {
  582. stack.length = stack.length - 2*n;
  583. vstack.length = vstack.length - n;
  584. lstack.length = lstack.length - n;
  585. }
  586. function lex() {
  587. var token;
  588. token = self.lexer.lex() || 1; // $end = 1
  589. // if token isn't its numeric value, convert
  590. if (typeof token !== 'number') {
  591. token = self.symbols_[token] || token;
  592. }
  593. return token;
  594. }
  595. var symbol, preErrorSymbol, state, action, a, r, yyval={},p,len,newState, expected;
  596. while (true) {
  597. // retreive state number from top of stack
  598. state = stack[stack.length-1];
  599. // use default actions if available
  600. if (this.defaultActions[state]) {
  601. action = this.defaultActions[state];
  602. } else {
  603. if (symbol == null)
  604. symbol = lex();
  605. // read action for current state and first input
  606. action = table[state] && table[state][symbol];
  607. }
  608. // handle parse error
  609. _handle_error:
  610. if (typeof action === 'undefined' || !action.length || !action[0]) {
  611. if (!recovering) {
  612. // Report error
  613. expected = [];
  614. for (p in table[state]) if (this.terminals_[p] && p > 2) {
  615. expected.push("'"+this.terminals_[p]+"'");
  616. }
  617. var errStr = '';
  618. if (this.lexer.showPosition) {
  619. errStr = 'Parse error on line '+(yylineno+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+expected.join(', ') + ", got '" + this.terminals_[symbol]+ "'";
  620. } else {
  621. errStr = 'Parse error on line '+(yylineno+1)+": Unexpected " +
  622. (symbol == 1 /*EOF*/ ? "end of input" :
  623. ("'"+(this.terminals_[symbol] || symbol)+"'"));
  624. }
  625. this.parseError(errStr,
  626. {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});
  627. }
  628. // just recovered from another error
  629. if (recovering == 3) {
  630. if (symbol == EOF) {
  631. throw new Error(errStr || 'Parsing halted.');
  632. }
  633. // discard current lookahead and grab another
  634. yyleng = this.lexer.yyleng;
  635. yytext = this.lexer.yytext;
  636. yylineno = this.lexer.yylineno;
  637. yyloc = this.lexer.yylloc;
  638. symbol = lex();
  639. }
  640. // try to recover from error
  641. while (1) {
  642. // check for error recovery rule in this state
  643. if ((TERROR.toString()) in table[state]) {
  644. break;
  645. }
  646. if (state == 0) {
  647. throw new Error(errStr || 'Parsing halted.');
  648. }
  649. popStack(1);
  650. state = stack[stack.length-1];
  651. }
  652. preErrorSymbol = symbol; // save the lookahead token
  653. symbol = TERROR; // insert generic error symbol as new lookahead
  654. state = stack[stack.length-1];
  655. action = table[state] && table[state][TERROR];
  656. recovering = 3; // allow 3 real symbols to be shifted before reporting a new error
  657. }
  658. // this shouldn't happen, unless resolve defaults are off
  659. if (action[0] instanceof Array && action.length > 1) {
  660. throw new Error('Parse Error: multiple actions possible at state: '+state+', token: '+symbol);
  661. }
  662. switch (action[0]) {
  663. case 1: // shift
  664. //this.shiftCount++;
  665. stack.push(symbol);
  666. vstack.push(this.lexer.yytext);
  667. lstack.push(this.lexer.yylloc);
  668. stack.push(action[1]); // push state
  669. symbol = null;
  670. if (!preErrorSymbol) { // normal execution/no error
  671. yyleng = this.lexer.yyleng;
  672. yytext = this.lexer.yytext;
  673. yylineno = this.lexer.yylineno;
  674. yyloc = this.lexer.yylloc;
  675. if (recovering > 0)
  676. recovering--;
  677. } else { // error just occurred, resume old lookahead f/ before error
  678. symbol = preErrorSymbol;
  679. preErrorSymbol = null;
  680. }
  681. break;
  682. case 2: // reduce
  683. //this.reductionCount++;
  684. len = this.productions_[action[1]][1];
  685. // perform semantic action
  686. yyval.$ = vstack[vstack.length-len]; // default to $$ = $1
  687. // default location, uses first token for firsts, last for lasts
  688. yyval._$ = {
  689. first_line: lstack[lstack.length-(len||1)].first_line,
  690. last_line: lstack[lstack.length-1].last_line,
  691. first_column: lstack[lstack.length-(len||1)].first_column,
  692. last_column: lstack[lstack.length-1].last_column
  693. };
  694. r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);
  695. if (typeof r !== 'undefined') {
  696. return r;
  697. }
  698. // pop off stack
  699. if (len) {
  700. stack = stack.slice(0,-1*len*2);
  701. vstack = vstack.slice(0, -1*len);
  702. lstack = lstack.slice(0, -1*len);
  703. }
  704. stack.push(this.productions_[action[1]][0]); // push nonterminal (reduce)
  705. vstack.push(yyval.$);
  706. lstack.push(yyval._$);
  707. // goto new state = table[STATE][NONTERMINAL]
  708. newState = table[stack[stack.length-2]][stack[stack.length-1]];
  709. stack.push(newState);
  710. break;
  711. case 3: // accept
  712. return true;
  713. }
  714. }
  715. return true;
  716. }};/* Jison generated lexer */
  717. var lexer = (function(){
  718. var lexer = ({EOF:1,
  719. parseError:function parseError(str, hash) {
  720. if (this.yy.parseError) {
  721. this.yy.parseError(str, hash);
  722. } else {
  723. throw new Error(str);
  724. }
  725. },
  726. setInput:function (input) {
  727. this._input = input;
  728. this._more = this._less = this.done = false;
  729. this.yylineno = this.yyleng = 0;
  730. this.yytext = this.matched = this.match = '';
  731. this.conditionStack = ['INITIAL'];
  732. this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0};
  733. return this;
  734. },
  735. input:function () {
  736. var ch = this._input[0];
  737. this.yytext+=ch;
  738. this.yyleng++;
  739. this.match+=ch;
  740. this.matched+=ch;
  741. var lines = ch.match(/\n/);
  742. if (lines) this.yylineno++;
  743. this._input = this._input.slice(1);
  744. return ch;
  745. },
  746. unput:function (ch) {
  747. this._input = ch + this._input;
  748. return this;
  749. },
  750. more:function () {
  751. this._more = true;
  752. return this;
  753. },
  754. pastInput:function () {
  755. var past = this.matched.substr(0, this.matched.length - this.match.length);
  756. return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
  757. },
  758. upcomingInput:function () {
  759. var next = this.match;
  760. if (next.length < 20) {
  761. next += this._input.substr(0, 20-next.length);
  762. }
  763. return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, "");
  764. },
  765. showPosition:function () {
  766. var pre = this.pastInput();
  767. var c = new Array(pre.length + 1).join("-");
  768. return pre + this.upcomingInput() + "\n" + c+"^";
  769. },
  770. next:function () {
  771. if (this.done) {
  772. return this.EOF;
  773. }
  774. if (!this._input) this.done = true;
  775. var token,
  776. match,
  777. col,
  778. lines;
  779. if (!this._more) {
  780. this.yytext = '';
  781. this.match = '';
  782. }
  783. var rules = this._currentRules();
  784. for (var i=0;i < rules.length; i++) {
  785. match = this._input.match(this.rules[rules[i]]);
  786. if (match) {
  787. lines = match[0].match(/\n.*/g);
  788. if (lines) this.yylineno += lines.length;
  789. this.yylloc = {first_line: this.yylloc.last_line,
  790. last_line: this.yylineno+1,
  791. first_column: this.yylloc.last_column,
  792. last_column: lines ? lines[lines.length-1].length-1 : this.yylloc.last_column + match[0].length}
  793. this.yytext += match[0];
  794. this.match += match[0];
  795. this.matches = match;
  796. this.yyleng = this.yytext.length;
  797. this._more = false;
  798. this._input = this._input.slice(match[0].length);
  799. this.matched += match[0];
  800. token = this.performAction.call(this, this.yy, this, rules[i],this.conditionStack[this.conditionStack.length-1]);
  801. if (token) return token;
  802. else return;
  803. }
  804. }
  805. if (this._input === "") {
  806. return this.EOF;
  807. } else {
  808. this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(),
  809. {text: "", token: null, line: this.yylineno});
  810. }
  811. },
  812. lex:function lex() {
  813. var r = this.next();
  814. if (typeof r !== 'undefined') {
  815. return r;
  816. } else {
  817. return this.lex();
  818. }
  819. },
  820. begin:function begin(condition) {
  821. this.conditionStack.push(condition);
  822. },
  823. popState:function popState() {
  824. return this.conditionStack.pop();
  825. },
  826. _currentRules:function _currentRules() {
  827. return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules;
  828. },
  829. topState:function () {
  830. return this.conditionStack[this.conditionStack.length-2];
  831. },
  832. pushState:function begin(condition) {
  833. this.begin(condition);
  834. }});
  835. lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
  836. var YYSTATE=YY_START;
  837. switch($avoiding_name_collisions) {
  838. case 0:/* skip whitespace */
  839. break;
  840. case 1:return 20
  841. break;
  842. case 2:return 19
  843. break;
  844. case 3:return 8
  845. break;
  846. case 4:return 9
  847. break;
  848. case 5:return 6
  849. break;
  850. case 6:return 7
  851. break;
  852. case 7:return 11
  853. break;
  854. case 8:return 13
  855. break;
  856. case 9:return 10
  857. break;
  858. case 10:return 12
  859. break;
  860. case 11:return 14
  861. break;
  862. case 12:return 15
  863. break;
  864. case 13:return 16
  865. break;
  866. case 14:return 17
  867. break;
  868. case 15:return 18
  869. break;
  870. case 16:return 5
  871. break;
  872. case 17:return 'INVALID'
  873. break;
  874. }
  875. };
  876. lexer.rules = [/^\s+/,/^[0-9]+(\.[0-9]+)?\b/,/^n\b/,/^\|\|/,/^&&/,/^\?/,/^:/,/^<=/,/^>=/,/^</,/^>/,/^!=/,/^==/,/^%/,/^\(/,/^\)/,/^$/,/^./];
  877. lexer.conditions = {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17],"inclusive":true}};return lexer;})()
  878. parser.lexer = lexer;
  879. return parser;
  880. })();
  881. // End parser
  882. // Handle node, amd, and global systems
  883. if (typeof exports !== 'undefined') {
  884. if (typeof module !== 'undefined' && module.exports) {
  885. exports = module.exports = Jed;
  886. }
  887. exports.Jed = Jed;
  888. }
  889. else {
  890. if (typeof define === 'function' && define.amd) {
  891. define('jed', function() {
  892. return Jed;
  893. });
  894. }
  895. // Leak a global regardless of module system
  896. root['Jed'] = Jed;
  897. }
  898. })(this);