# Source: core/messageformat.js

    /* global module */
    /**
     * @class MessageFormat
     * @classdesc see [messageformat.js](https://github.com/SlexAxton/messageformat.js)
     */
    function MessageFormat(locale, pluralFunc) {
        var fallbackLocale;

        if (locale && pluralFunc) {
            MessageFormat.locale[locale] = pluralFunc;
        }

        // Defaults
        fallbackLocale = locale = locale || "en";
        pluralFunc = pluralFunc || MessageFormat.locale[fallbackLocale = MessageFormat.Utils.getFallbackLocale(locale)];

        if (!pluralFunc) {
            throw new Error("Plural Function not found for locale: " + locale);
        }

        // Own Properties
        this.pluralFunc = pluralFunc;
        this.locale = locale;
        this.fallbackLocale = fallbackLocale;
    }

    // Set up the locales object. Add in english by default
    MessageFormat.locale = {
        "en": function(n) {
            if (n === 1) {
                return "one";
            }
            return "other";
        }
    };

    // Build out our basic SafeString type
    // more or less stolen from Handlebars by @wycats
    MessageFormat.SafeString = function(string) {
        this.string = string;
    };

    MessageFormat.SafeString.prototype.toString = function() {
        return this.string.toString();
    };

    MessageFormat.Utils = {
        numSub: function(string, key, depth) {
            // make sure that it's not an escaped octothorpe
            return string.replace(/^#|[^\\]#/g, function(m) {
                var prefix = m && m.length === 2 ? m.charAt(0) : '';
                return prefix + '" + (function(){ var x = ' +
                    key + ';\nif( isNaN(x) ){\nthrow new Error("MessageFormat: `"+lastkey_' + depth + '+"` isnt a number.");\n}\nreturn x;\n})() + "';
            });
        },
        escapeExpression: function(string) {
            var escape = {
                    "\n": "\\n",
                    "\"": '\\"'
                },
                badChars = /[\n"]/g,
                possible = /[\n"]/,
                escapeChar = function(chr) {
                    return escape[chr] || "&amp;";
                };

            // Don't escape SafeStrings, since they're already safe
            if (string instanceof MessageFormat.SafeString) {
                return string.toString();
            } else if (string === null || string === false) {
                return "";
            }

            if (!possible.test(string)) {
                return string;
            }
            return string.replace(badChars, escapeChar);
        },
        getFallbackLocale: function(locale) {
            var tagSeparator = locale.indexOf("-") >= 0 ? "-" : "_";

            // Lets just be friends, fallback through the language tags
            while (!MessageFormat.locale.hasOwnProperty(locale)) {
                locale = locale.substring(0, locale.lastIndexOf(tagSeparator));
                if (locale.length === 0) {
                    return null;
                }
            }

            return locale;
        }
    };

    // This is generated and pulled in for browsers.
    var mparser = (function() {
        /* Generated by PEG.js 0.6.2 (http://pegjs.majda.cz/). */

        var result = {
            /*
             * Parses the input with a generated parser. If the parsing is successfull,
             * returns a value explicitly or implicitly specified by the grammar from
             * which the parser was generated (see |PEG.buildParser|). If the parsing is
             * unsuccessful, throws |PEG.parser.SyntaxError| describing the error.
             */
            parse: function(input, startRule) {

                var pos = 0;
                var reportMatchFailures = true;
                var rightmostMatchFailuresPos = 0;
                var rightmostMatchFailuresExpected = [];
                var cache = {};
                var parse_elementFormat;

                function padLeft(input, padding, length) {
                    var result = input;

                    var padLength = length - input.length;
                    for (var i = 0; i < padLength; i++) {
                        result = padding + result;
                    }

                    return result;
                }

                function escape(ch) {
                    var length, escapeChar,
                      charCode = ch.charCodeAt(0);

                    if (charCode <= 0xFF) {
                        escapeChar = 'x';
                        length = 2;
                    } else {
                        escapeChar = 'u';
                        length = 4;
                    }

                    return '\\' + escapeChar + padLeft(charCode.toString(16).toUpperCase(), '0', length);
                }

                function quote(s) {
                    /*
                     * ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a
                     * string literal except for the closing quote character, backslash,
                     * carriage return, line separator, paragraph separator, and line feed.
                     * Any character may appear in the form of an escape sequence.
                     */
                    return '"' + s
                        .replace(/\\/g, '\\\\') // backslash
                        .replace(/"/g, '\\"') // closing quote character
                        .replace(/\r/g, '\\r') // carriage return
                        .replace(/\n/g, '\\n') // line feed
                        .replace(/[\x80-\uFFFF]/g, escape) + // non-ASCII characters
                        '"';
                }

                function matchFailed(failure) {
                    if (pos < rightmostMatchFailuresPos) {
                        return;
                    }

                    if (pos > rightmostMatchFailuresPos) {
                        rightmostMatchFailuresPos = pos;
                        rightmostMatchFailuresExpected = [];
                    }

                    rightmostMatchFailuresExpected.push(failure);
                }

                function parse_whitespace() {
                    var cacheKey = 'whitespace@' + pos;
                    var cachedResult = cache[cacheKey];
                    if (cachedResult) {
                        pos = cachedResult.nextPos;
                        return cachedResult.result;
                    }

                    var result0;
                    if (input.substr(pos).match(/^[   \n\r]/) !== null) {
                        result0 = input.charAt(pos);
                        pos++;
                    } else {
                        result0 = null;
                        if (reportMatchFailures) {
                            matchFailed("[  \\n\\r]");
                        }
                    }

                    cache[cacheKey] = {
                        nextPos: pos,
                        result: result0
                    };
                    return result0;
                }

                function parse__() {
                    var cacheKey = '_@' + pos;
                    var cachedResult = cache[cacheKey];
                    if (cachedResult) {
                        pos = cachedResult.nextPos;
                        return cachedResult.result;
                    }

                    var savedReportMatchFailures = reportMatchFailures;
                    reportMatchFailures = false;
                    var savedPos0 = pos;
                    var result1 = [];
                    var result3 = parse_whitespace();
                    while (result3 !== null) {
                        result1.push(result3);
                        result3 = parse_whitespace();
                    }
                    var result2 = result1 !== null ?
                        (function(w) {
                            return w.join('');
                        })(result1) :
                        null;

                    var result0;
                    if (result2 !== null) {
                        result0 = result2;
                    } else {
                        result0 = null;
                        pos = savedPos0;
                    }
                    reportMatchFailures = savedReportMatchFailures;
                    if (reportMatchFailures && result0 === null) {
                        matchFailed("whitespace");
                    }

                    cache[cacheKey] = {
                        nextPos: pos,
                        result: result0
                    };
                    return result0;
                }

                function parse_digits() {
                    var cacheKey = 'digits@' + pos;
                    var cachedResult = cache[cacheKey];
                    if (cachedResult) {
                        pos = cachedResult.nextPos;
                        return cachedResult.result;
                    }

                    var savedPos0 = pos;
                    var result3;
                    if (input.substr(pos).match(/^[0-9]/) !== null) {
                        result3 = input.charAt(pos);
                        pos++;
                    } else {
                        result3 = null;
                        if (reportMatchFailures) {
                            matchFailed("[0-9]");
                        }
                    }

                    var result1;
                    if (result3 !== null) {
                        result1 = [];
                        while (result3 !== null) {
                            result1.push(result3);
                            if (input.substr(pos).match(/^[0-9]/) !== null) {
                                result3 = input.charAt(pos);
                                pos++;
                            } else {
                                result3 = null;
                                if (reportMatchFailures) {
                                    matchFailed("[0-9]");
                                }
                            }
                        }
                    } else {
                        result1 = null;
                    }

                    var result2 = result1 !== null ?
                        (function(ds) {
                            return parseInt((ds.join('')), 10);
                        })(result1) :
                        null;

                    var result0;
                    if (result2 !== null) {
                        result0 = result2;
                    } else {
                        result0 = null;
                        pos = savedPos0;
                    }

                    cache[cacheKey] = {
                        nextPos: pos,
                        result: result0
                    };
                    return result0;
                }

                function parse_offsetPattern() {
                    var cacheKey = 'offsetPattern@' + pos;
                    var cachedResult = cache[cacheKey];
                    if (cachedResult) {
                        pos = cachedResult.nextPos;
                        return cachedResult.result;
                    }

                    var savedPos0 = pos;
                    var savedPos1 = pos;
                    var result3 = parse__();
                    var result1;
                    if (result3 !== null) {
                        var result4;
                        if (input.substr(pos, 6) === "offset") {
                            result4 = "offset";
                            pos += 6;
                        } else {
                            result4 = null;
                            if (reportMatchFailures) {
                                matchFailed("\"offset\"");
                            }
                        }
                        if (result4 !== null) {
                            var result5 = parse__();
                            if (result5 !== null) {
                                var result6;
                                if (input.substr(pos, 1) === ":") {
                                    result6 = ":";
                                    pos += 1;
                                } else {
                                    result6 = null;
                                    if (reportMatchFailures) {
                                        matchFailed("\":\"");
                                    }
                                }
                                if (result6 !== null) {
                                    var result7 = parse__();
                                    if (result7 !== null) {
                                        var result8 = parse_digits();
                                        if (result8 !== null) {
                                            var result9 = parse__();
                                            if (result9 !== null) {
                                                result1 = [result3, result4, result5, result6, result7, result8, result9];
                                            } else {
                                                result1 = null;
                                                pos = savedPos1;
                                            }
                                        } else {
                                            result1 = null;
                                            pos = savedPos1;
                                        }
                                    } else {
                                        result1 = null;
                                        pos = savedPos1;
                                    }
                                } else {
                                    result1 = null;
                                    pos = savedPos1;
                                }
                            } else {
                                result1 = null;
                                pos = savedPos1;
                            }
                        } else {
                            result1 = null;
                            pos = savedPos1;
                        }
                    } else {
                        result1 = null;
                        pos = savedPos1;
                    }
                    var result2 = result1 !== null ?
                        (function(d) {
                            return d;
                        })(result1[5]) :
                        null;

                    var result0;
                    if (result2 !== null) {
                        result0 = result2;
                    } else {
                        result0 = null;
                        pos = savedPos0;
                    }

                    cache[cacheKey] = {
                        nextPos: pos,
                        result: result0
                    };
                    return result0;
                }

                function parse_id() {
                    var cacheKey = 'id@' + pos;
                    var cachedResult = cache[cacheKey];
                    if (cachedResult) {
                        pos = cachedResult.nextPos;
                        return cachedResult.result;
                    }

                    var savedPos0 = pos;
                    var savedPos1 = pos;
                    var result3 = parse__();
                    var result1;
                    if (result3 !== null) {
                        var result4;
                        if (input.substr(pos).match(/^[a-zA-Z$_]/) !== null) {
                            result4 = input.charAt(pos);
                            pos++;
                        } else {
                            result4 = null;
                            if (reportMatchFailures) {
                                matchFailed("[a-zA-Z$_]");
                            }
                        }
                        if (result4 !== null) {
                            var result5 = [];
                            var result7;
                            if (input.substr(pos).match(/^[^  \n\r,.+={}]/) !== null) {
                                result7 = input.charAt(pos);
                                pos++;
                            } else {
                                result7 = null;
                                if (reportMatchFailures) {
                                    matchFailed("[^   \\n\\r,.+={}]");
                                }
                            }
                            while (result7 !== null) {
                                result5.push(result7);
                                if (input.substr(pos).match(/^[^  \n\r,.+={}]/) !== null) {
                                    result7 = input.charAt(pos);
                                    pos++;
                                } else {
                                    result7 = null;
                                    if (reportMatchFailures) {
                                        matchFailed("[^   \\n\\r,.+={}]");
                                    }
                                }
                            }
                            if (result5 !== null) {
                                var result6 = parse__();
                                if (result6 !== null) {
                                    result1 = [result3, result4, result5, result6];
                                } else {
                                    result1 = null;
                                    pos = savedPos1;
                                }
                            } else {
                                result1 = null;
                                pos = savedPos1;
                            }
                        } else {
                            result1 = null;
                            pos = savedPos1;
                        }
                    } else {
                        result1 = null;
                        pos = savedPos1;
                    }
                    var result2 = result1 !== null ?
                        (function(s1, s2) {
                            return s1 + (s2 ? s2.join('') : '');
                        })(result1[1], result1[2]) :
                        null;

                    var result0;
                    if (result2 !== null) {
                        result0 = result2;
                    } else {
                        result0 = null;
                        pos = savedPos0;
                    }

                    cache[cacheKey] = {
                        nextPos: pos,
                        result: result0
                    };
                    return result0;
                }

                function parse_stringKey() {
                    var cacheKey = 'stringKey@' + pos;
                    var cachedResult = cache[cacheKey];
                    if (cachedResult) {
                        pos = cachedResult.nextPos;
                        return cachedResult.result;
                    }

                    var savedPos2 = pos;
                    var result7 = parse_id();
                    var result8 = result7 !== null ?
                        (function(i) {
                            return i;
                        })(result7) :
                        null;
                    var result6;
                    if (result8 !== null) {
                        result6 = result8;
                    } else {
                        result6 = null;
                        pos = savedPos2;
                    }
                    var result0;
                    if (result6 !== null) {
                        result0 = result6;
                    } else {
                        var savedPos0 = pos;
                        var savedPos1 = pos;
                        var result4;
                        if (input.substr(pos, 1) === "=") {
                            result4 = "=";
                            pos += 1;
                        } else {
                            result4 = null;
                            if (reportMatchFailures) {
                                matchFailed("\"=\"");
                            }
                        }
                        var result2;
                        if (result4 !== null) {
                            var result5 = parse_digits();
                            if (result5 !== null) {
                                result2 = [result4, result5];
                            } else {
                                result2 = null;
                                pos = savedPos1;
                            }
                        } else {
                            result2 = null;
                            pos = savedPos1;
                        }
                        var result3 = result2 !== null ?
                            (function(d) {
                                return d;
                            })(result2[1]) :
                            null;

                        var result1;
                        if (result3 !== null) {
                            result1 = result3;
                        } else {
                            result1 = null;
                            pos = savedPos0;
                        }
                        if (result1 !== null) {
                            result0 = result1;
                        } else {
                            result0 = null;
                        }
                    }

                    cache[cacheKey] = {
                        nextPos: pos,
                        result: result0
                    };

                    return result0;
                }

                function parse_hexDigit() {
                    var cacheKey = 'hexDigit@' + pos;
                    var cachedResult = cache[cacheKey];
                    if (cachedResult) {
                        pos = cachedResult.nextPos;
                        return cachedResult.result;
                    }

                    var result0;
                    if (input.substr(pos).match(/^[0-9a-fA-F]/) !== null) {
                        result0 = input.charAt(pos);
                        pos++;
                    } else {
                        result0 = null;
                        if (reportMatchFailures) {
                            matchFailed("[0-9a-fA-F]");
                        }
                    }

                    cache[cacheKey] = {
                        nextPos: pos,
                        result: result0
                    };
                    return result0;
                }

                var charPattern = "^[^{}\\\0-     \n\r]", // jshint ignore:line
                    charPatternMatch = new RegExp("/" + charPattern + "/");
                function parse_char() {
                    var cacheKey = 'char@' + pos;
                    var cachedResult = cache[cacheKey];

                    if (cachedResult) {
                        pos = cachedResult.nextPos;
                        return cachedResult.result;
                    }

                    var savedPos5 = pos;
                    var result19;
                    if (input.substr(pos).match(charPattern) !== null) {
                        result19 = input.charAt(pos);
                        pos++;
                    } else {
                        result19 = null;
                        if (reportMatchFailures) {
                            matchFailed(charPattern);
                        }
                    }

                    var result20 = result19 !== null ?
                        (function(x) {
                            return x;
                        })(result19) :
                        null;

                    var result18;
                    if (result20 !== null) {
                        result18 = result20;
                    } else {
                        result18 = null;
                        pos = savedPos5;
                    }

                    var result0;
                    if (result18 !== null) {
                        result0 = result18;
                    } else {
                        var savedPos4 = pos;
                        var result16;
                        if (input.substr(pos, 2) === "\\#") {
                            result16 = "\\#";
                            pos += 2;
                        } else {
                            result16 = null;
                            if (reportMatchFailures) {
                                matchFailed("\"\\\\#\"");
                            }
                        }
                        var result17 = result16 !== null ?
                            (function() {
                                return "\\#";
                            })() :
                            null;
                        var result15;
                        if (result17 !== null) {
                            result15 = result17;
                        } else {
                            result15 = null;
                            pos = savedPos4;
                        }
                        if (result15 !== null) {
                            result0 = result15;
                        } else {
                            var savedPos3 = pos;
                            var result13;
                            if (input.substr(pos, 2) === "\\{") {
                                result13 = "\\{";
                                pos += 2;
                            } else {
                                result13 = null;
                                if (reportMatchFailures) {
                                    matchFailed("\"\\\\{\"");
                                }
                            }
                            var result14 = result13 !== null ?
                                (function() {
                                    return "\u007B";
                                })() :
                                null;
                            var result12;
                            if (result14 !== null) {
                                result12 = result14;
                            } else {
                                result12 = null;
                                pos = savedPos3;
                            }
                            if (result12 !== null) {
                                result0 = result12;
                            } else {
                                var savedPos2 = pos;
                                var result10;
                                if (input.substr(pos, 2) === "\\}") {
                                    result10 = "\\}";
                                    pos += 2;
                                } else {
                                    result10 = null;
                                    if (reportMatchFailures) {
                                        matchFailed("\"\\\\}\"");
                                    }
                                }
                                var result11 = result10 !== null ?
                                    (function() {
                                        return "\u007D";
                                    })() :
                                    null;

                                var result9;
                                if (result11 !== null) {
                                    result9 = result11;
                                } else {
                                    result9 = null;
                                    pos = savedPos2;
                                }
                                if (result9 !== null) {
                                    result0 = result9;
                                } else {
                                    var savedPos0 = pos;
                                    var savedPos1 = pos;
                                    var result4;
                                    if (input.substr(pos, 2) === "\\u") {
                                        result4 = "\\u";
                                        pos += 2;
                                    } else {
                                        result4 = null;
                                        if (reportMatchFailures) {
                                            matchFailed("\"\\\\u\"");
                                        }
                                    }
                                    var result2;
                                    if (result4 !== null) {
                                        var result5 = parse_hexDigit();
                                        if (result5 !== null) {
                                            var result6 = parse_hexDigit();
                                            if (result6 !== null) {
                                                var result7 = parse_hexDigit();
                                                if (result7 !== null) {
                                                    var result8 = parse_hexDigit();
                                                    if (result8 !== null) {
                                                        result2 = [result4, result5, result6, result7, result8];
                                                    } else {
                                                        result2 = null;
                                                        pos = savedPos1;
                                                    }
                                                } else {
                                                    result2 = null;
                                                    pos = savedPos1;
                                                }
                                            } else {
                                                result2 = null;
                                                pos = savedPos1;
                                            }
                                        } else {
                                            result2 = null;
                                            pos = savedPos1;
                                        }
                                    } else {
                                        result2 = null;
                                        pos = savedPos1;
                                    }

                                    var result3 = result2 !== null ?
                                        (function(h1, h2, h3, h4) {
                                            return String.fromCharCode(parseInt("0x" + h1 + h2 + h3 + h4));
                                        })(result2[1], result2[2], result2[3], result2[4]) :
                                        null;

                                    var result1;
                                    if (result3 !== null) {
                                        result1 = result3;
                                    } else {
                                        result1 = null;
                                        pos = savedPos0;
                                    }
                                    if (result1 !== null) {
                                        result0 = result1;
                                    } else {
                                        result0 = null;
                                    }
                                }
                            }
                        }
                    }

                    cache[cacheKey] = {
                        nextPos: pos,
                        result: result0
                    };

                    return result0;
                }

                function parse_chars() {
                    var cacheKey = 'chars@' + pos;
                    var cachedResult = cache[cacheKey];
                    if (cachedResult) {
                        pos = cachedResult.nextPos;
                        return cachedResult.result;
                    }

                    var savedPos0 = pos;
                    var result3 = parse_char();
                    var result1;
                    if (result3 !== null) {
                        result1 = [];
                        while (result3 !== null) {
                            result1.push(result3);
                            result3 = parse_char();
                        }
                    } else {
                        result1 = null;
                    }
                    var result2 = result1 !== null ?
                        (function(chars) {
                            return chars.join('');
                        })(result1) :
                        null;

                    var result0;
                    if (result2 !== null) {
                        result0 = result2;
                    } else {
                        result0 = null;
                        pos = savedPos0;
                    }

                    cache[cacheKey] = {
                        nextPos: pos,
                        result: result0
                    };
                    return result0;
                }

                function parse_string() {
                    var cacheKey = 'string@' + pos;
                    var cachedResult = cache[cacheKey];
                    if (cachedResult) {
                        pos = cachedResult.nextPos;
                        return cachedResult.result;
                    }

                    var savedPos0 = pos;
                    var savedPos1 = pos;
                    var result3 = parse__();
                    var result1;
                    if (result3 !== null) {
                        var result4 = [];
                        var savedPos2 = pos;
                        var result6 = parse__();
                        var result8;
                        var result5;
                        var result7;
                        if (result6 !== null) {
                            result7 = parse_chars();
                            if (result7 !== null) {
                                result8 = parse__();
                                if (result8 !== null) {
                                    result5 = [result6, result7, result8];
                                } else {
                                    result5 = null;
                                    pos = savedPos2;
                                }
                            } else {
                                result5 = null;
                                pos = savedPos2;
                            }
                        } else {
                            result5 = null;
                            pos = savedPos2;
                        }
                        while (result5 !== null) {
                            result4.push(result5);
                            savedPos2 = pos;
                            result6 = parse__();
                            if (result6 !== null) {
                                result7 = parse_chars();
                                if (result7 !== null) {
                                    result8 = parse__();
                                    if (result8 !== null) {
                                        result5 = [result6, result7, result8];
                                    } else {
                                        result5 = null;
                                        pos = savedPos2;
                                    }
                                } else {
                                    result5 = null;
                                    pos = savedPos2;
                                }
                            } else {
                                result5 = null;
                                pos = savedPos2;
                            }
                        }
                        if (result4 !== null) {
                            result1 = [result3, result4];
                        } else {
                            result1 = null;
                            pos = savedPos1;
                        }
                    } else {
                        result1 = null;
                        pos = savedPos1;
                    }
                    var result2 = result1 !== null ?
                        (function(ws, s) {
                            var tmp = [];
                            for (var i = 0; i < s.length; ++i) {
                                for (var j = 0; j < s[i].length; ++j) {
                                    tmp.push(s[i][j]);
                                }
                            }
                            return {
                                type: "string",
                                val: ws + tmp.join('')
                            };
                        })(result1[0], result1[1]) :
                        null;

                    var result0;
                    if (result2 !== null) {
                        result0 = result2;
                    } else {
                        result0 = null;
                        pos = savedPos0;
                    }

                    cache[cacheKey] = {
                        nextPos: pos,
                        result: result0
                    };
                    return result0;
                }

                function parse_messageFormatElement() {
                    var cacheKey = 'messageFormatElement@' + pos;
                    var cachedResult = cache[cacheKey];
                    if (cachedResult) {
                        pos = cachedResult.nextPos;
                        return cachedResult.result;
                    }

                    var savedPos0 = pos;
                    var savedPos1 = pos;
                    var result3 = parse_id();
                    var result1;
                    if (result3 !== null) {
                        var savedPos2 = pos;
                        var result6;
                        if (input.substr(pos, 1) === ",") {
                            result6 = ",";
                            pos += 1;
                        } else {
                            result6 = null;
                            if (reportMatchFailures) {
                                matchFailed("\",\"");
                            }
                        }
                        var result5;
                        if (result6 !== null) {
                            var result7 = parse_elementFormat();
                            if (result7 !== null) {
                                result5 = [result6, result7];
                            } else {
                                result5 = null;
                                pos = savedPos2;
                            }
                        } else {
                            result5 = null;
                            pos = savedPos2;
                        }
                        var result4 = result5 !== null ? result5 : '';
                        if (result4 !== null) {
                            result1 = [result3, result4];
                        } else {
                            result1 = null;
                            pos = savedPos1;
                        }
                    } else {
                        result1 = null;
                        pos = savedPos1;
                    }
                    var result2 = result1 !== null ?
                        (function(argIdx, efmt) {
                            var res = {
                                type: "messageFormatElement",
                                argumentIndex: argIdx
                            };
                            if (efmt && efmt.length) {
                                res.elementFormat = efmt[1];
                            } else {
                                res.output = true;
                            }
                            return res;
                        })(result1[0], result1[1]) :
                        null;

                    var result0;
                    if (result2 !== null) {
                        result0 = result2;
                    } else {
                        result0 = null;
                        pos = savedPos0;
                    }

                    cache[cacheKey] = {
                        nextPos: pos,
                        result: result0
                    };
                    return result0;
                }

                function parse_messageFormatPatternRight() {
                    var cacheKey = 'messageFormatPatternRight@' + pos;
                    var cachedResult = cache[cacheKey];
                    if (cachedResult) {
                        pos = cachedResult.nextPos;
                        return cachedResult.result;
                    }

                    var savedPos0 = pos;
                    var savedPos1 = pos;
                    var result3;
                    if (input.substr(pos, 1) === "{") {
                        result3 = "{";
                        pos += 1;
                    } else {
                        result3 = null;
                        if (reportMatchFailures) {
                            matchFailed("\"{\"");
                        }
                    }
                    var result1;
                    if (result3 !== null) {
                        var result4 = parse__();
                        if (result4 !== null) {
                            var result5 = parse_messageFormatElement();
                            if (result5 !== null) {
                                var result6 = parse__();
                                if (result6 !== null) {
                                    var result7;
                                    if (input.substr(pos, 1) === "}") {
                                        result7 = "}";
                                        pos += 1;
                                    } else {
                                        result7 = null;
                                        if (reportMatchFailures) {
                                            matchFailed("\"}\"");
                                        }
                                    }
                                    if (result7 !== null) {
                                        var result8 = parse_string();
                                        if (result8 !== null) {
                                            result1 = [result3, result4, result5, result6, result7, result8];
                                        } else {
                                            result1 = null;
                                            pos = savedPos1;
                                        }
                                    } else {
                                        result1 = null;
                                        pos = savedPos1;
                                    }
                                } else {
                                    result1 = null;
                                    pos = savedPos1;
                                }
                            } else {
                                result1 = null;
                                pos = savedPos1;
                            }
                        } else {
                            result1 = null;
                            pos = savedPos1;
                        }
                    } else {
                        result1 = null;
                        pos = savedPos1;
                    }
                    var result2 = result1 !== null ?
                        (function(mfe, s1) {
                            var res = [];
                            if (mfe) {
                                res.push(mfe);
                            }
                            if (s1 && s1.val) {
                                res.push(s1);
                            }
                            return {
                                type: "messageFormatPatternRight",
                                statements: res
                            };
                        })(result1[2], result1[5]) :
                        null;

                    var result0;
                    if (result2 !== null) {
                        result0 = result2;
                    } else {
                        result0 = null;
                        pos = savedPos0;
                    }

                    cache[cacheKey] = {
                        nextPos: pos,
                        result: result0
                    };
                    return result0;
                }

                function parse_messageFormatPattern() {
                    var cacheKey = 'messageFormatPattern@' + pos;
                    var cachedResult = cache[cacheKey];
                    if (cachedResult) {
                        pos = cachedResult.nextPos;
                        return cachedResult.result;
                    }

                    var savedPos0 = pos;
                    var savedPos1 = pos;
                    var result1;
                    var result3 = parse_string();
                    if (result3 !== null) {
                        var result4 = [];
                        var result5 = parse_messageFormatPatternRight();
                        while (result5 !== null) {
                            result4.push(result5);
                            result5 = parse_messageFormatPatternRight();
                        }
                        if (result4 !== null) {
                            result1 = [result3, result4];
                        } else {
                            result1 = null;
                            pos = savedPos1;
                        }
                    } else {
                        result1 = null;
                        pos = savedPos1;
                    }
                    var result0;
                    var result2 = result1 !== null ?
                        (function(s1, inner) {
                            var st = [];
                            if (s1 && s1.val) {
                                st.push(s1);
                            }
                            for (var i in inner) {
                                if (inner.hasOwnProperty(i)) {
                                    st.push(inner[i]);
                                }
                            }
                            return {
                                type: 'messageFormatPattern',
                                statements: st
                            };
                        })(result1[0], result1[1]) :
                        null;
                    if (result2 !== null) {
                        result0 = result2;
                    } else {
                        result0 = null;
                        pos = savedPos0;
                    }

                    cache[cacheKey] = {
                        nextPos: pos,
                        result: result0
                    };
                    return result0;
                }

                function parse_pluralForms() {
                    var cacheKey = 'pluralForms@' + pos;
                    var cachedResult = cache[cacheKey];
                    if (cachedResult) {
                        pos = cachedResult.nextPos;
                        return cachedResult.result;
                    }

                    var savedPos0 = pos;
                    var savedPos1 = pos;
                    var result1;
                    var result3 = parse__();
                    if (result3 !== null) {
                        var result4 = parse_stringKey();
                        if (result4 !== null) {
                            var result5 = parse__();
                            if (result5 !== null) {
                                var result6;
                                if (input.substr(pos, 1) === "{") {
                                    result6 = "{";
                                    pos += 1;
                                } else {
                                    result6 = null;
                                    if (reportMatchFailures) {
                                        matchFailed("\"{\"");
                                    }
                                }
                                if (result6 !== null) {
                                    var result7 = parse__();
                                    if (result7 !== null) {
                                        var result8 = parse_messageFormatPattern();
                                        if (result8 !== null) {
                                            var result9 = parse__();
                                            if (result9 !== null) {
                                                var result10;
                                                if (input.substr(pos, 1) === "}") {
                                                    result10 = "}";
                                                    pos += 1;
                                                } else {
                                                    result10 = null;
                                                    if (reportMatchFailures) {
                                                        matchFailed("\"}\"");
                                                    }
                                                }
                                                if (result10 !== null) {
                                                    result1 = [result3, result4, result5, result6, result7, result8, result9, result10];
                                                } else {
                                                    result1 = null;
                                                    pos = savedPos1;
                                                }
                                            } else {
                                                result1 = null;
                                                pos = savedPos1;
                                            }
                                        } else {
                                            result1 = null;
                                            pos = savedPos1;
                                        }
                                    } else {
                                        result1 = null;
                                        pos = savedPos1;
                                    }
                                } else {
                                    result1 = null;
                                    pos = savedPos1;
                                }
                            } else {
                                result1 = null;
                                pos = savedPos1;
                            }
                        } else {
                            result1 = null;
                            pos = savedPos1;
                        }
                    } else {
                        result1 = null;
                        pos = savedPos1;
                    }

                    var result2 = result1 !== null ?
                        (function(k, mfp) {
                            return {
                                type: "pluralForms",
                                key: k,
                                val: mfp
                            };
                        })(result1[1], result1[5]) :
                        null;

                    var result0;
                    if (result2 !== null) {
                        result0 = result2;
                    } else {
                        result0 = null;
                        pos = savedPos0;
                    }

                    cache[cacheKey] = {
                        nextPos: pos,
                        result: result0
                    };
                    return result0;
                }

                function parse_selectFormatPattern() {
                    var cacheKey = 'selectFormatPattern@' + pos;
                    var cachedResult = cache[cacheKey];
                    if (cachedResult) {
                        pos = cachedResult.nextPos;
                        return cachedResult.result;
                    }

                    var savedPos0 = pos;
                    var result1 = [];
                    var result3 = parse_pluralForms();
                    while (result3 !== null) {
                        result1.push(result3);
                        result3 = parse_pluralForms();
                    }
                    var result2 = result1 !== null ?
                        (function(pf) {
                            return {
                                type: "selectFormatPattern",
                                pluralForms: pf
                            };
                        })(result1) :
                        null;

                    var result0;
                    if (result2 !== null) {
                        result0 = result2;
                    } else {
                        result0 = null;
                        pos = savedPos0;
                    }

                    cache[cacheKey] = {
                        nextPos: pos,
                        result: result0
                    };
                    return result0;
                }

                function parse_selectStyle() {
                    var cacheKey = 'selectStyle@' + pos;
                    var cachedResult = cache[cacheKey];
                    if (cachedResult) {
                        pos = cachedResult.nextPos;
                        return cachedResult.result;
                    }

                    var savedPos0 = pos;
                    var result1 = parse_selectFormatPattern();
                    var result2 = result1 !== null ?
                        (function(sfp) {
                            return {
                                type: "selectStyle",
                                val: sfp
                            };
                        })(result1) :
                        null;

                    var result0;
                    if (result2 !== null) {
                        result0 = result2;
                    } else {
                        result0 = null;
                        pos = savedPos0;
                    }

                    cache[cacheKey] = {
                        nextPos: pos,
                        result: result0
                    };
                    return result0;
                }

                function parse_pluralFormatPattern() {
                    var cacheKey = 'pluralFormatPattern@' + pos;
                    var cachedResult = cache[cacheKey];
                    if (cachedResult) {
                        pos = cachedResult.nextPos;
                        return cachedResult.result;
                    }

                    var savedPos0 = pos;
                    var savedPos1 = pos;
                    var result6 = parse_offsetPattern();
                    var result3 = result6 !== null ? result6 : '';
                    var result1;
                    if (result3 !== null) {
                        var result4 = [];
                        var result5 = parse_pluralForms();
                        while (result5 !== null) {
                            result4.push(result5);
                            result5 = parse_pluralForms();
                        }
                        if (result4 !== null) {
                            result1 = [result3, result4];
                        } else {
                            result1 = null;
                            pos = savedPos1;
                        }
                    } else {
                        result1 = null;
                        pos = savedPos1;
                    }
                    var result2 = result1 !== null ?
                        (function(op, pf) {
                            var res = {
                                type: "pluralFormatPattern",
                                pluralForms: pf
                            };
                            if (op) {
                                res.offset = op;
                            } else {
                                res.offset = 0;
                            }
                            return res;
                        })(result1[0], result1[1]) :
                        null;

                    var result0;
                    if (result2 !== null) {
                        result0 = result2;
                    } else {
                        result0 = null;
                        pos = savedPos0;
                    }

                    cache[cacheKey] = {
                        nextPos: pos,
                        result: result0
                    };

                    return result0;
                }

                function parse_pluralStyle() {
                    var cacheKey = 'pluralStyle@' + pos;
                    var cachedResult = cache[cacheKey];
                    if (cachedResult) {
                        pos = cachedResult.nextPos;
                        return cachedResult.result;
                    }

                    var savedPos0 = pos;
                    var result1 = parse_pluralFormatPattern();
                    var result2 = result1 !== null ?
                        (function(pfp) {
                            return {
                                type: "pluralStyle",
                                val: pfp
                            };
                        })(result1) :
                        null;

                    var result0;
                    if (result2 !== null) {
                        result0 = result2;
                    } else {
                        result0 = null;
                        pos = savedPos0;
                    }

                    cache[cacheKey] = {
                        nextPos: pos,
                        result: result0
                    };
                    return result0;
                }

                parse_elementFormat = function () {
                    var cacheKey = 'elementFormat@' + pos;
                    var cachedResult = cache[cacheKey];
                    if (cachedResult) {
                        pos = cachedResult.nextPos;
                        return cachedResult.result;
                    }

                    var savedPos2 = pos;
                    var savedPos3 = pos;
                    var result14 = parse__();
                    var result12;
                    if (result14 !== null) {
                        var result15;
                        if (input.substr(pos, 6) === "plural") {
                            result15 = "plural";
                            pos += 6;
                        } else {
                            result15 = null;
                            if (reportMatchFailures) {
                                matchFailed("\"plural\"");
                            }
                        }
                        if (result15 !== null) {
                            var result16 = parse__();
                            if (result16 !== null) {
                                var result17;
                                if (input.substr(pos, 1) === ",") {
                                    result17 = ",";
                                    pos += 1;
                                } else {
                                    result17 = null;
                                    if (reportMatchFailures) {
                                        matchFailed("\",\"");
                                    }
                                }
                                if (result17 !== null) {
                                    var result18 = parse__();
                                    if (result18 !== null) {
                                        var result19 = parse_pluralStyle();
                                        if (result19 !== null) {
                                            var result20 = parse__();
                                            if (result20 !== null) {
                                                result12 = [result14, result15, result16, result17, result18, result19, result20];
                                            } else {
                                                result12 = null;
                                                pos = savedPos3;
                                            }
                                        } else {
                                            result12 = null;
                                            pos = savedPos3;
                                        }
                                    } else {
                                        result12 = null;
                                        pos = savedPos3;
                                    }
                                } else {
                                    result12 = null;
                                    pos = savedPos3;
                                }
                            } else {
                                result12 = null;
                                pos = savedPos3;
                            }
                        } else {
                            result12 = null;
                            pos = savedPos3;
                        }
                    } else {
                        result12 = null;
                        pos = savedPos3;
                    }
                    var result13 = result12 !== null ?
                        (function(t, s) {
                            return {
                                type: "elementFormat",
                                key: t,
                                val: s.val
                            };
                        })(result12[1], result12[5]) :
                        null;

                    var result11;
                    if (result13 !== null) {
                        result11 = result13;
                    } else {
                        result11 = null;
                        pos = savedPos2;
                    }

                    var result0;
                    var result2;
                    if (result11 !== null) {
                        result0 = result11;
                    } else {
                        var savedPos0 = pos;
                        var savedPos1 = pos;
                        var result4 = parse__();
                        if (result4 !== null) {
                            var result5;
                            if (input.substr(pos, 6) === "select") {
                                result5 = "select";
                                pos += 6;
                            } else {
                                result5 = null;
                                if (reportMatchFailures) {
                                    matchFailed("\"select\"");
                                }
                            }
                            if (result5 !== null) {
                                var result6 = parse__();
                                if (result6 !== null) {
                                    var result7;
                                    if (input.substr(pos, 1) === ",") {
                                        result7 = ",";
                                        pos += 1;
                                    } else {
                                        result7 = null;
                                        if (reportMatchFailures) {
                                            matchFailed("\",\"");
                                        }
                                    }
                                    if (result7 !== null) {
                                        var result8 = parse__();
                                        if (result8 !== null) {
                                            var result9 = parse_selectStyle();
                                            if (result9 !== null) {
                                                var result10 = parse__();
                                                if (result10 !== null) {
                                                    result2 = [result4, result5, result6, result7, result8, result9, result10];
                                                } else {
                                                    result2 = null;
                                                    pos = savedPos1;
                                                }
                                            } else {
                                                result2 = null;
                                                pos = savedPos1;
                                            }
                                        } else {
                                            result2 = null;
                                            pos = savedPos1;
                                        }
                                    } else {
                                        result2 = null;
                                        pos = savedPos1;
                                    }
                                } else {
                                    result2 = null;
                                    pos = savedPos1;
                                }
                            } else {
                                result2 = null;
                                pos = savedPos1;
                            }
                        } else {
                            result2 = null;
                            pos = savedPos1;
                        }
                        var result3 = result2 !== null ?
                            (function(t, s) {
                                return {
                                    type: "elementFormat",
                                    key: t,
                                    val: s.val
                                };
                            })(result2[1], result2[5]) :
                            null;

                        var result1;
                        if (result3 !== null) {
                            result1 = result3;
                        } else {
                            result1 = null;
                            pos = savedPos0;
                        }
                        if (result1 !== null) {
                            result0 = result1;
                        } else {
                            result0 = null;
                        }
                    }

                    cache[cacheKey] = {
                        nextPos: pos,
                        result: result0
                    };
                    return result0;
                };

                function parse_start() {
                    var cacheKey = 'start@' + pos;
                    var cachedResult = cache[cacheKey];
                    if (cachedResult) {
                        pos = cachedResult.nextPos;
                        return cachedResult.result;
                    }

                    var savedPos0 = pos;
                    var result0;
                    var result1 = parse_messageFormatPattern();
                    var result2 = result1 !== null ?
                        (function(messageFormatPattern) {
                            return {
                                type: "program",
                                program: messageFormatPattern
                            };
                        })(result1) :
                        null;
                    if (result2 !== null) {
                        result0 = result2;
                    } else {
                        result0 = null;
                        pos = savedPos0;
                    }

                    cache[cacheKey] = {
                        nextPos: pos,
                        result: result0
                    };
                    return result0;
                }

                function buildErrorMessage() {
                    function buildExpected(failuresExpected) {
                        failuresExpected.sort();

                        var lastFailure = null;
                        var failuresExpectedUnique = [];
                        for (var i = 0; i < failuresExpected.length; i++) {
                            if (failuresExpected[i] !== lastFailure) {
                                failuresExpectedUnique.push(failuresExpected[i]);
                                lastFailure = failuresExpected[i];
                            }
                        }

                        switch (failuresExpectedUnique.length) {
                            case 0:
                                return 'end of input';
                            case 1:
                                return failuresExpectedUnique[0];
                            default:
                                return failuresExpectedUnique.slice(0, failuresExpectedUnique.length - 1).join(', ') +
                                    ' or ' +
                                    failuresExpectedUnique[failuresExpectedUnique.length - 1];
                        }
                    }

                    var expected = buildExpected(rightmostMatchFailuresExpected);
                    var actualPos = Math.max(pos, rightmostMatchFailuresPos);
                    var actual = actualPos < input.length ?
                        quote(input.charAt(actualPos)) :
                        'end of input';

                    return 'Expected ' + expected + ' but ' + actual + ' found.';
                }

                function computeErrorPosition() {
                    /*
                     * The first idea was to use |String.split| to break the input up to the
                     * error position along newlines and derive the line and column from
                     * there. However IE's |split| implementation is so broken that it was
                     * enough to prevent it.
                     */

                    var line = 1;
                    var column = 1;
                    var seenCR = false;

                    for (var i = 0; i < rightmostMatchFailuresPos; i++) {
                        var ch = input.charAt(i);
                        if (ch === '\n') {
                            if (!seenCR) {
                                line++;
                            }
                            column = 1;
                            seenCR = false;
                        } else if (ch === '\r' | ch === '\u2028' || ch === '\u2029') {
                            line++;
                            column = 1;
                            seenCR = true;
                        } else {
                            column++;
                            seenCR = false;
                        }
                    }

                    return {
                        line: line,
                        column: column
                    };
                }

                var parseFunctions = {
                    "_": parse__,
                    "char": parse_char,
                    "chars": parse_chars,
                    "digits": parse_digits,
                    "elementFormat": parse_elementFormat,
                    "hexDigit": parse_hexDigit,
                    "id": parse_id,
                    "messageFormatElement": parse_messageFormatElement,
                    "messageFormatPattern": parse_messageFormatPattern,
                    "messageFormatPatternRight": parse_messageFormatPatternRight,
                    "offsetPattern": parse_offsetPattern,
                    "pluralFormatPattern": parse_pluralFormatPattern,
                    "pluralForms": parse_pluralForms,
                    "pluralStyle": parse_pluralStyle,
                    "selectFormatPattern": parse_selectFormatPattern,
                    "selectStyle": parse_selectStyle,
                    "start": parse_start,
                    "string": parse_string,
                    "stringKey": parse_stringKey,
                    "whitespace": parse_whitespace
                };

                if (startRule !== undefined) {
                    if (parseFunctions[startRule] === undefined) {
                        throw new Error("Invalid rule name: " + quote(startRule) + ".");
                    }
                } else {
                    startRule = "start";
                }

                var result = parseFunctions[startRule]();

                /*
                 * The parser is now in one of the following three states:
                 *
                 * 1. The parser successfully parsed the whole input.
                 *
                 *    - |result !== null|
                 *    - |pos === input.length|
                 *    - |rightmostMatchFailuresExpected| may or may not contain something
                 *
                 * 2. The parser successfully parsed only a part of the input.
                 *
                 *    - |result !== null|
                 *    - |pos < input.length|
                 *    - |rightmostMatchFailuresExpected| may or may not contain something
                 *
                 * 3. The parser did not successfully parse any part of the input.
                 *
                 *   - |result === null|
                 *   - |pos === 0|
                 *   - |rightmostMatchFailuresExpected| contains at least one failure
                 *
                 * All code following this comment (including called functions) must
                 * handle these states.
                 */
                if (result === null || pos !== input.length) {
                    var errorPosition = computeErrorPosition();
                    throw new this.SyntaxError(
                        buildErrorMessage(),
                        errorPosition.line,
                        errorPosition.column
                    );
                }

                return result;
            },

            /* Returns the parser source code. */
            toSource: function() {
                return this._source;
            }
        };

        /* Thrown when a parser encounters a syntax error. */

        result.SyntaxError = function(message, line, column) {
            this.name = 'SyntaxError';
            this.message = message;
            this.line = line;
            this.column = column;
        };

        result.SyntaxError.prototype = Error.prototype;

        return result;
    })();

    MessageFormat.prototype.parse = function() {
        // Bind to itself so error handling works
        return mparser.parse.apply(mparser, arguments);
    };

    MessageFormat.prototype.precompile = function(ast) {
        var self = this,
            needOther = false,
            fp = {
                begin: 'function(d){\nvar r = "";\n',
                end: "return r;\n}"
            };

        function interpMFP(ast, data) {
            // Set some default data
            data = data || {};
            var s = '',
                res, i, tmp, lastkeyname;

            switch (ast.type) {
                case 'program':
                    return interpMFP(ast.program);
                case 'messageFormatPattern':
                    for (i = 0; i < ast.statements.length; ++i) {
                        s += interpMFP(ast.statements[i], data);
                    }
                    return fp.begin + s + fp.end;
                case 'messageFormatPatternRight':
                    for (i = 0; i < ast.statements.length; ++i) {
                        s += interpMFP(ast.statements[i], data);
                    }
                    return s;
                case 'messageFormatElement':
                    data.pf_count = data.pf_count || 0;
                    s += 'if(!d){\nthrow new Error("MessageFormat: No data passed to function.");\n}\n';
                    if (ast.output) {
                        s += 'r += d["' + ast.argumentIndex + '"];\n';
                    } else {
                        lastkeyname = 'lastkey_' + (data.pf_count + 1);
                        s += 'var ' + lastkeyname + ' = "' + ast.argumentIndex + '";\n';
                        s += 'var k_' + (data.pf_count + 1) + '=d[' + lastkeyname + '];\n';
                        s += interpMFP(ast.elementFormat, data);
                    }
                    return s;
                case 'elementFormat':
                    if (ast.key === 'select') {
                        s += interpMFP(ast.val, data);
                        s += 'r += (pf_' +
                            data.pf_count +
                            '[ k_' + (data.pf_count + 1) + ' ] || pf_' + data.pf_count + '[ "other" ])( d );\n';
                    } else if (ast.key === 'plural') {
                        s += interpMFP(ast.val, data);
                        s += 'if ( pf_' + (data.pf_count) + '[ k_' + (data.pf_count + 1) + ' + "" ] ) {\n';
                        s += 'r += pf_' + data.pf_count + '[ k_' + (data.pf_count + 1) + ' + "" ]( d ); \n';
                        s += '}\nelse {\n';
                        s += 'r += (pf_' +
                            data.pf_count +
                            '[ MessageFormat.locale["' +
                            self.fallbackLocale +
                            '"]( k_' + (data.pf_count + 1) + ' - off_' + (data.pf_count) + ' ) ] || pf_' + data.pf_count + '[ "other" ] )( d );\n';
                        s += '}\n';
                    }
                    return s;
                    /* // Unreachable cases.
                    case 'pluralStyle':
                    case 'selectStyle':*/
                case 'pluralFormatPattern':
                    data.pf_count = data.pf_count || 0;
                    s += 'var off_' + data.pf_count + ' = ' + ast.offset + ';\n';
                    s += 'var pf_' + data.pf_count + ' = { \n';
                    needOther = true;
                    // We're going to simultaneously check to make sure we hit the required 'other' option.

                    for (i = 0; i < ast.pluralForms.length; ++i) {
                        if (ast.pluralForms[i].key === 'other') {
                            needOther = false;
                        }
                        if (tmp) {
                            s += ',\n';
                        } else {
                            tmp = 1;
                        }
                        res = JSON.parse(JSON.stringify(data));
                        res.pf_count++;
                        s += '"' + ast.pluralForms[i].key + '" : ' + interpMFP(ast.pluralForms[i].val, res);
                    }
                    s += '\n};\n';
                    if (needOther) {
                        throw new Error("No 'other' form found in pluralFormatPattern " + data.pf_count);
                    }
                    return s;
                case 'selectFormatPattern':

                    data.pf_count = data.pf_count || 0;
                    s += 'var off_' + data.pf_count + ' = 0;\n';
                    s += 'var pf_' + data.pf_count + ' = { \n';
                    needOther = true;

                    for (i = 0; i < ast.pluralForms.length; ++i) {
                        if (ast.pluralForms[i].key === 'other') {
                            needOther = false;
                        }
                        if (tmp) {
                            s += ',\n';
                        } else {
                            tmp = 1;
                        }
                        res = JSON.parse(JSON.stringify(data));
                        res.pf_count++;
                        s += '"' + ast.pluralForms[i].key + '" : ' + interpMFP(ast.pluralForms[i].val, res);
                    }
                    s += '\n};\n';
                    if (needOther) {
                        throw new Error("No 'other' form found in selectFormatPattern " + data.pf_count);
                    }
                    return s;
                    /* // Unreachable
                    case 'pluralForms':
                    */
                case 'string':
                    return 'r += "' + MessageFormat.Utils.numSub(
                        MessageFormat.Utils.escapeExpression(ast.val),
                        'k_' + data.pf_count + ' - off_' + (data.pf_count - 1),
                        data.pf_count
                    ) + '";\n';
                default:
                    throw new Error('Bad AST type: ' + ast.type);
            }
        }
        return interpMFP(ast);
    };

    MessageFormat.prototype.compile = function(message) {
        /* jshint evil: true */
        return new Function('MessageFormat',
            'return ' +
            this.precompile(
                this.parse(message)
            )
        )(MessageFormat);
        /* jshint evil: false */
    };

    module.exports = MessageFormat;