Skip to content

Instantly share code, notes, and snippets.

@rxaviers
Created November 2, 2016 11:45
Show Gist options
  • Save rxaviers/0e57ed723f28800c06e1c07e6c069f1d to your computer and use it in GitHub Desktop.
Save rxaviers/0e57ed723f28800c06e1c07e6c069f1d to your computer and use it in GitHub Desktop.
commit dc4986747f50003c4093bb17ddb22813ce16ac6f
Author: Nikola Kovacs <nikola.kovacs@gmail.com>
Date: Wed Nov 2 12:37:28 2016 +0100
diff --git a/compiler.js b/compiler.js
index d0f9b1a..3cd2e44 100644
--- a/compiler.js
+++ b/compiler.js
@@ -1,51 +1,85 @@
-var reserved = require('reserved-words');
-var parse = require('messageformat-parser').parse;
+define([
+ "../core",
+ "messageformat-parser",
+ "reserved-words"
+], function( Globalize, Parser, reserved ) {
-
-/** Creates a new message compiler. Called internally from {@link MessageFormat#compile}.
+/** Creates a new message compiler.
*
* @class
- * @param {MessageFormat} mf - A MessageFormat instance
- * @property {object} locales - The locale identifiers that are used by the compiled functions
- * @property {object} runtime - Names of the core runtime functions that are used by the compiled functions
- * @property {object} formatters - The formatter functions that are used by the compiled functions
+ * @param {object} context - Context (this) for formatter functions
+ * @param {array} fmtFuncs - Formatter functions
+ * @property {object} locales - The locale identifiers used by the compiled functions
+ * @property {object} runtime - Names of the core runtime functions used by the compiled functions
+ * @property {array} formatters - The formatter functions used by the compiled functions
*/
-function Compiler(mf) {
- this.mf = mf;
- this.lc = null;
+function Compiler( context, fmtFuncs ) {
+ this._globalize = Globalize;
+ this.fmt = fmtFuncs;
this.locales = {};
this.runtime = {};
- this.formatters = {};
+ this.formatters = [];
+ this.context = context;
}
-module.exports = Compiler;
+/** Enable or disable the addition of Unicode control characters to all input
+ * to preserve the integrity of the output when mixing LTR and RTL text.
+ *
+ * @see http://cldr.unicode.org/development/development-process/design-proposals/bidi-handling-of-structured-text
+ *
+ * @memberof Compiler
+ * @param {boolean} [enable=true]
+ * @returns {Compiler} The Compiler instance, to allow for chaining
+ */
+Compiler.prototype.setBiDiSupport = function( enable ) {
+ this.bidiSupport = !!enable || ( typeof enable === "undefined" );
+ return this;
+};
+
+/** According to the ICU MessageFormat spec, a `#` character directly inside a
+ * `plural` or `selectordinal` statement should be replaced by the number
+ * matching the surrounding statement. By default, messageformat.js will
+ * replace `#` signs with the value of the nearest surrounding `plural` or
+ * `selectordinal` statement.
+ *
+ * Set this to true to follow the stricter ICU MessageFormat spec, and to
+ * throw a runtime error if `#` is used with non-numeric input.
+ *
+ * @memberof Compiler
+ * @param {boolean} [enable=true]
+ * @returns {Compiler} The Compiler instance, to allow for chaining
+ */
+Compiler.prototype.setStrictNumberSign = function( enable ) {
+ this.strictNumberSign = !!enable || ( typeof enable === "undefined" );
+ // TODO
+ // this.runtime.setStrictNumber(this.strictNumberSign);
+ return this;
+};
-/** Utility function for quoting an Object's key value iff required
+/** Utility function for quoting an Object's key value if required
*
* Quotes the key if it contains invalid characters or is an
* ECMAScript 3rd Edition reserved word (for IE8).
*/
-Compiler.propname = function(key, obj) {
- if (/^[A-Z_$][0-9A-Z_$]*$/i.test(key) &&
- ['break', 'continue', 'delete', 'else', 'for', 'function', 'if', 'in', 'new',
- 'return', 'this', 'typeof', 'var', 'void', 'while', 'with', 'case', 'catch',
- 'default', 'do', 'finally', 'instanceof', 'switch', 'throw', 'try'].indexOf(key) < 0) {
- return obj ? obj + '.' + key : key;
+Compiler.propname = function( key, obj ) {
+ if ( /^[A-Z_$][0-9A-Z_$]*$/i.test( key ) &&
+ [ "break", "continue", "delete", "else", "for", "function", "if", "in", "new",
+ "return", "this", "typeof", "var", "void", "while", "with", "case", "catch",
+ "default", "do", "finally", "instanceof", "switch", "throw", "try" ].indexOf( key ) < 0 ) {
+ return obj ? obj + "." + key : key;
} else {
- var jkey = JSON.stringify(key);
- return obj ? obj + '[' + jkey + ']' : jkey;
+ var jkey = JSON.stringify( key );
+ return obj ? obj + "[" + jkey + "]" : jkey;
}
-}
-
+};
-/** Utility function for escaping a function name iff required
+/** Utility function for escaping a function name if required
*/
-Compiler.funcname = function(key) {
- var fn = key.trim().replace(/\W+/g, '_');
- return reserved.check(fn, 'es2015', true) || /^\d/.test(fn) ? '_' + fn : fn;
-}
-
+Compiler.funcname = function( key ) {
+ var fn = key.trim().replace( /\W+/g, "_" );
+ return reserved.check( fn, "es2015", true ) || /^\d/.test( fn ) ? "_" + fn : fn;
+};
/** Utility formatter function for enforcing Bidi Structured Text by using UCC
*
@@ -56,89 +90,95 @@ Compiler.funcname = function(key) {
* cd cldr-misc-full/main/
* grep characterOrder -r . | tr '"/' '\t' | cut -f2,6 | grep -C4 right-to-left
*/
-Compiler.bidiMarkText = function(text, locale) {
- function isLocaleRTL(locale) {
- var rtlLanguages = ['ar', 'ckb', 'fa', 'he', 'ks($|[^bfh])', 'lrc', 'mzn',
- 'pa-Arab', 'ps', 'ug', 'ur', 'uz-Arab', 'yi'];
- return new RegExp('^' + rtlLanguages.join('|^')).test(locale);
+Compiler.bidiMarkText = function( text, locale ) {
+ function isLocaleRTL( locale ) {
+ var rtlLanguages = [ "ar", "ckb", "fa", "he", "ks($|[^bfh])", "lrc", "mzn",
+ "pa-Arab", "ps", "ug", "ur", "uz-Arab", "yi" ];
+ return new RegExp( "^" + rtlLanguages.join( "|^" ) ).test( locale );
}
- var mark = JSON.stringify(isLocaleRTL(locale) ? '\u200F' : '\u200E');
- return mark + ' + ' + text + ' + ' + mark;
-}
-
+ var mark = JSON.stringify( isLocaleRTL( locale ) ? "\u200F" : "\u200E" );
+ return mark + " + " + text + " + " + mark;
+};
/** @private */
-Compiler.prototype.cases = function(token, plural) {
+Compiler.prototype.cases = function( token, plural ) {
var needOther = true;
- var r = token.cases.map(function(c) {
- if (c.key === 'other') needOther = false;
- var s = c.tokens.map(function(tok) { return this.token(tok, plural); }, this);
- return Compiler.propname(c.key) + ': ' + (s.join(' + ') || '""');
- }, this);
- if (needOther) throw new Error("No 'other' form found in " + JSON.stringify(token));
- return '{ ' + r.join(', ') + ' }';
-}
-
+ var r = token.cases.map( function( c ) {
+ if ( c.key === "other" ) {
+ needOther = false;
+ }
+ var s = c.tokens.map( function( tok ) { return this.token( tok, plural ); }, this );
+ return Compiler.propname( c.key ) + ": " + ( s.join( " + " ) || "\"\"" );
+ }, this );
+ if ( needOther ) {
+ throw new Error( "No 'other' form found in " + JSON.stringify( token ) );
+ }
+ return "{ " + r.join( ", " ) + " }";
+};
/** @private */
-Compiler.prototype.token = function(token, plural) {
- if (typeof token == 'string') return JSON.stringify(token);
+Compiler.prototype.token = function( token, plural ) {
+ if ( typeof token === "string" ) {
+ return JSON.stringify( token );
+ }
- var fn, args = [ Compiler.propname(token.arg, 'd') ];
- switch (token.type) {
- case 'argument':
- return this.mf.bidiSupport ? Compiler.bidiMarkText(args[0], this.lc) : args[0];
+ var fn, args = [ Compiler.propname( token.arg, "d" ) ];
+ switch ( token.type ) {
+ case "argument":
+ return this.bidiSupport ? Compiler.bidiMarkText( args[0], this.lc ) : args[0];
- case 'select':
- fn = 'select';
- args.push(this.cases(token, this.mf.strictNumberSign ? null : plural));
+ case "select":
+ fn = "select";
+ args.push( this.cases( token, this.strictNumberSign ? null : plural ) );
this.runtime.select = true;
break;
- case 'selectordinal':
- fn = 'plural';
- args.push(0, Compiler.funcname(this.lc), this.cases(token, token), 1);
- this.locales[this.lc] = true;
+ case "selectordinal":
+ fn = "plural";
+ args.push( 0, Compiler.funcname( this.lc ), this.cases( token, token ), 1 );
+ this.locales[ this.lc ] = true;
this.runtime.plural = true;
break;
- case 'plural':
- fn = 'plural';
- args.push(token.offset || 0, Compiler.funcname(this.lc), this.cases(token, token));
- this.locales[this.lc] = true;
+ case "plural":
+ fn = "plural";
+ args.push(
+ token.offset || 0,
+ Compiler.funcname( this.lc ), this.cases( token, token )
+ );
+ this.locales[ this.lc ] = true;
this.runtime.plural = true;
break;
- case 'function':
- if (this.mf.intlSupport && !(token.key in this.mf.fmt) && (token.key in this.mf.constructor.formatters)) {
- var fmt = this.mf.constructor.formatters[token.key];
- this.mf.fmt[token.key] = (typeof fmt(this.mf) == 'function') ? fmt(this.mf) : fmt;
+ case "function":
+ if ( !this.fmt[token.key] ) {
+ throw new Error( "Formatting function " +
+ JSON.stringify( token.key ) + " not found!" );
}
- if (!this.mf.fmt[token.key]) throw new Error('Formatting function ' + JSON.stringify(token.key) + ' not found!');
- args.push(JSON.stringify(this.lc));
- if (token.params) switch (token.params.length) {
- case 0: break;
- case 1: args.push(JSON.stringify(token.params[0])); break;
- default: args.push(JSON.stringify(token.params)); break;
- }
- fn = Compiler.propname(token.key, 'fmt');
- this.formatters[token.key] = true;
+ fn = "fmt[" + JSON.stringify( this.formatters.length ) + "]";
+ this.formatters.push( this.fmt[token.key].apply( this.context, token.params || [] ) );
break;
- case 'octothorpe':
- if (!plural) return '"#"';
- fn = 'number';
- args = [ Compiler.propname(plural.arg, 'd'), JSON.stringify(plural.arg) ];
- if (plural.offset) args.push(plural.offset);
+ case "octothorpe":
+ if ( !plural ) {
+ return "\"#\"";
+ }
+ fn = "number";
+ args = [ Compiler.propname( plural.arg, "d" ), JSON.stringify( plural.arg ) ];
+ if ( plural.offset ) {
+ args.push( plural.offset );
+ }
+
this.runtime.number = true;
break;
}
- if (!fn) throw new Error('Parser error for token ' + JSON.stringify(token));
- return fn + '(' + args.join(', ') + ')';
+ if ( !fn ) {
+ throw new Error( "Parser error for token " + JSON.stringify( token ) );
+ }
+ return fn + "(" + args.join( ", " ) + ")";
};
-
/** Recursively compile a string or a tree of strings to JavaScript function sources
*
* If `src` is an object with a key that is also present in `plurals`, the key
@@ -150,18 +190,15 @@ Compiler.prototype.token = function(token, plural) {
* @param {string} lc - the default locale
* @param {object} plurals - a map of pluralization keys for all available locales
*/
-Compiler.prototype.compile = function(src, lc, plurals) {
- if (typeof src != 'object') {
+Compiler.prototype.compile = function( src, lc ) {
this.lc = lc;
- var pc = plurals[lc] || { cardinal: [], ordinal: [] };
- var r = parse(src, pc).map(function(token) { return this.token(token); }, this);
- return 'function(d) { return ' + (r.join(' + ') || '""') + '; }';
- } else {
- var result = {};
- for (var key in src) {
- var lcKey = plurals.hasOwnProperty(key) ? key : lc;
- result[key] = this.compile(src[key], lcKey, plurals);
- }
- return result;
- }
-}
\ No newline at end of file
+
+ // TODO: pc is only needed for validation, disable for now.
+ var pc = { cardinal: [], ordinal: [] };
+ var r = Parser.parse( src, pc ).map( function( token ) { return this.token( token ); }, this );
+ return "function(d) { return " + ( r.join( " + " ) || "\"\"" ) + "; }";
+};
+
+return Compiler;
+
+});
\ No newline at end of file
commit 6e8cca28ef5cc132ac159c31daa83f83d24d2297
Author: Nikola Kovacs <nikola.kovacs@gmail.com>
Date: Wed Nov 2 12:40:28 2016 +0100
diff --git a/runtime.js b/runtime.js
index 18d371a..36859a9 100644
--- a/runtime.js
+++ b/runtime.js
@@ -1,21 +1,9 @@
-var Compiler = require('./compiler');
+define([], function() {
-
-/** A set of utility functions that are called by the compiled Javascript
- * functions, these are included locally in the output of {@link
- * MessageFormat#compile compile()}.
- *
- * @class
- * @param {MessageFormat} mf - A MessageFormat instance
- */
-function Runtime(mf) {
- this.mf = mf;
- this.setStrictNumber(mf.strictNumberSign);
+function Runtime( strictNumberSign ) {
+ this.setStrictNumber( strictNumberSign );
}
-module.exports = Runtime;
-
-
/** Utility function for `#` in plural rules
*
* Will throw an Error if `value` has a non-numeric value and `offset` is
@@ -27,21 +15,26 @@ module.exports = Runtime;
* @param {number} [offset=0] - An optional offset, set by the surrounding context
* @returns {number|string} The result of applying the offset to the input value
*/
-function defaultNumber(value, name, offset) {
- if (!offset) return value;
- if (isNaN(value)) throw new Error('Can\'t apply offset:' + offset + ' to argument `' + name +
- '` with non-numerical value ' + JSON.stringify(value) + '.');
+function defaultNumber( value, name, offset ) {
+ if ( !offset ) {
+ return value;
+ }
+ if ( isNaN( value ) ) {
+ throw new Error( "Can't apply offset:" + offset + " to argument `" + name +
+ "` with non-numerical value " + JSON.stringify( value ) + "." );
+ }
return value - offset;
}
-
/** @private */
-function strictNumber(value, name, offset) {
- if (isNaN(value)) throw new Error('Argument `' + name + '` has non-numerical value ' + JSON.stringify(value) + '.');
- return value - (offset || 0);
+function strictNumber( value, name, offset ) {
+ if ( isNaN( value ) ) {
+ throw new Error( "Argument `" + name + "` has non-numerical value " +
+ JSON.stringify( value ) + "." );
+ }
+ return value - ( offset || 0 );
}
-
/** Set how strictly the {@link number} method parses its input.
*
* According to the ICU MessageFormat spec, `#` can only be used to replace a
@@ -54,10 +47,9 @@ function strictNumber(value, name, offset) {
*
* @param {boolean} [enable=false]
*/
-Runtime.prototype.setStrictNumber = function(enable) {
+Runtime.prototype.setStrictNumber = function( enable ) {
this.number = enable ? strictNumber : defaultNumber;
-}
-
+};
/** Utility function for `{N, plural|selectordinal, ...}`
*
@@ -68,14 +60,19 @@ Runtime.prototype.setStrictNumber = function(enable) {
* @param {?boolean} isOrdinal - If true, use ordinal rather than cardinal rules
* @returns {string} The result of the pluralization
*/
-Runtime.prototype.plural = function(value, offset, lcfunc, data, isOrdinal) {
- if ({}.hasOwnProperty.call(data, value)) return data[value];
- if (offset) value -= offset;
- var key = lcfunc(value, isOrdinal);
- if (key in data) return data[key];
+Runtime.prototype.plural = function( value, offset, lcfunc, data, isOrdinal ) {
+ if ( {}.hasOwnProperty.call( data, value ) ) {
+ return data[value];
+ }
+ if ( offset ) {
+ value -= offset;
+ }
+ var key = lcfunc( value, isOrdinal );
+ if ( key in data ) {
+ return data[key];
+ }
return data.other;
-}
-
+};
/** Utility function for `{N, select, ...}`
*
@@ -83,36 +80,13 @@ Runtime.prototype.plural = function(value, offset, lcfunc, data, isOrdinal) {
* @param {Object.<string,string>} data - The object from which results are looked up
* @returns {string} The result of the select statement
*/
-Runtime.prototype.select = function(value, data) {
- if ({}.hasOwnProperty.call(data, value)) return data[value];
+Runtime.prototype.select = function( value, data ) {
+ if ( {}.hasOwnProperty.call( data, value ) ) {
+ return data[value];
+ }
return data.other;
-}
+};
+return Runtime;
-/** @private */
-Runtime.prototype.toString = function(pluralFuncs, compiler) {
- function _stringify(o, level) {
- if (typeof o != 'object') {
- var funcStr = o.toString().replace(/^(function )\w*/, '$1');
- var indent = /([ \t]*)\S.*$/.exec(funcStr);
- return indent ? funcStr.replace(new RegExp('^' + indent[1], 'mg'), '') : funcStr;
- }
- var s = [];
- for (var i in o) {
- if (level == 0) s.push('var ' + i + ' = ' + _stringify(o[i], level + 1) + ';\n');
- else s.push(Compiler.propname(i) + ': ' + _stringify(o[i], level + 1));
- }
- if (level == 0) return s.join('');
- if (s.length == 0) return '{}';
- var indent = ' '; while (--level) indent += ' ';
- return '{\n' + s.join(',\n').replace(/^/gm, indent) + '\n}';
- }
-
- var obj = {};
- Object.keys(compiler.locales).forEach(function(lc) { obj[Compiler.funcname(lc)] = pluralFuncs[lc]; });
- Object.keys(compiler.runtime).forEach(function(fn) { obj[fn] = this[fn]; }, this);
- var fmtKeys = Object.keys(compiler.formatters);
- var fmt = this.mf.fmt;
- if (fmtKeys.length) obj.fmt = fmtKeys.reduce(function(o, key) { o[key] = fmt[key]; return o; }, {});
- return _stringify(obj, 0);
-}
\ No newline at end of file
+});
\ No newline at end of file
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment