Created
November 2, 2016 11:45
-
-
Save rxaviers/0e57ed723f28800c06e1c07e6c069f1d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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