UNPKG

make-plural-compiler

Version:

Translates Unicode CLDR pluralization rules to executable JavaScript

145 lines (117 loc) 4.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Compiler = void 0; var _parser = require("./parser.js"); var _tests = require("./tests.js"); function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } class Compiler { static load(...args) { args.forEach(cldr => { const data = cldr && cldr.supplemental || null; if (!data) throw new Error('Data does not appear to be CLDR data'); Compiler.rules = { cardinal: data['plurals-type-cardinal'] || Compiler.rules.cardinal, ordinal: data['plurals-type-ordinal'] || Compiler.rules.ordinal }; }); return Compiler; } static getRules(type, locale) { if (locale.length) { const cat = Compiler.rules[type]; if (locale in cat) return cat[locale]; const lc0 = locale.toLowerCase(); for (let lc in cat) if (lc.toLowerCase() === lc0) return cat[lc]; } return null; } /** * @param {string} src * @returns {string[]} */ static parseExamples(src) { return src.join(' ').replace(/^[ ,]+|[ ,…]+$/g, '').replace(/(0\.[0-9])~(1\.[1-9])/g, '$1 1.0 $2').split(/[ ,~…]+/).filter(n => !n.includes('c')); } constructor(lc, { cardinals, ordinals } = Compiler) { if (!lc) throw new Error('A locale is required'); if (!cardinals && !ordinals) throw new Error('At least one type of plural is required'); this.lc = lc; this.categories = { cardinal: [], ordinal: [] }; this.examples = { cardinal: {}, ordinal: {} }; this.parser = new _parser.Parser(); this.types = { cardinals, ordinals }; } compile() { if (!this.fn) { this.fn = this.buildFunction(); this.fn.toString = () => // The /*``*/ is present in Node 8 output, due to // https://bugs.chromium.org/p/v8/issues/detail?id=2470 Function.prototype.toString.call(this.fn).replace(/^function(?: \w+)\(([^)]+)\)/, (_, args) => `(${args.replace('/*``*/', '').trim()}) =>`).replace(/{\s*return\s+([^{}]*);\s*}$/, '$1'); this.test = () => { for (const type of ['cardinal', 'ordinal']) for (const [cat, values] of Object.entries(this.examples[type])) (0, _tests.testCat)(this.lc, type, cat, values, this.fn); }; } return this.fn; } buildBody(type, req) { let cases = []; const rules = Compiler.getRules(type, this.lc); if (!rules) { if (req) throw new Error(`Locale "${this.lc}" ${type} rules not found`); this.categories[type] = ['other']; return "'other'"; } for (let r in rules) { const [cond, ...examples] = rules[r].trim().split(/\s*@\w*/); const cat = r.replace('pluralRule-count-', ''); if (cond) cases.push([this.parser.parse(cond), cat]); this.examples[type][cat] = Compiler.parseExamples(examples); } this.categories[type] = cases.map(c => c[1]).concat('other'); if (cases.length === 1) { return `${cases[0][0]} ? '${cases[0][1]}' : 'other'`; } else { return [...cases.map(c => `${c[0]} ? '${c[1]}'`), "'other'"].join('\n : '); } } buildFunction() { const { cardinals, ordinals } = this.types; let body = ''; if (ordinals && cardinals) { const ordBody = this.buildBody('ordinal', false); const cardBody = this.buildBody('cardinal', true); if (ordBody === cardBody) body = ` return ${cardBody};`;else body = ` if (ord) return ${ordBody};\n return ${cardBody};`; } else { const pt = cardinals ? 'cardinal' : 'ordinal'; body = ` return ${this.buildBody(pt, true)};`; } const vars = this.parser.vars(); if (vars) body = ` ${vars};\n${body}`; const args = ordinals && cardinals ? 'n, ord' : 'n'; return new Function(args, body); // eslint-disable-line no-new-func } } exports.Compiler = Compiler; _defineProperty(Compiler, "cardinals", true); _defineProperty(Compiler, "ordinals", false); _defineProperty(Compiler, "rules", { cardinal: {}, ordinal: {} });