diff --git a/test/benchmark.html b/test/benchmark.html
new file mode 100644
index 0000000..70b5ffb
--- /dev/null
+++ b/test/benchmark.html
@@ -0,0 +1,13 @@
+
+
+
+ mustache.js benchmark tests
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/benchmark.js b/test/benchmark.js
new file mode 100644
index 0000000..8c9fb3e
--- /dev/null
+++ b/test/benchmark.js
@@ -0,0 +1,34 @@
+var tmpl = "This is the story of guys who work on a project\n" +
+ "called {{project}}. Their names were {{#people}}{{firstName}} and {{/people}}\n" +
+ "they both enjoyed working on {{project}}.\n\n" +
+ "{{#people}}\n" +
+ "{{>personPet}}\n" +
+ "{{/people}}";
+var partials = {
+ personPet: "{{firstName}} {{lastName}} {{#pet}} owned a {{species}}. Its name was {{name}}.{{/pet}}{{^pet}}didn't own a pet.{{/pet}}"
+};
+var data = {
+ project: "Handlebars",
+ people: [
+ { firstName: "Yehuda", lastName: "Katz" },
+ { firstName: "Alan", lastName: "Johnson", pet: { species: "cat", name: "Luke" } }
+ ]
+}
+
+$(function() {
+ var bench = new Benchmark('to_html', function() {
+ Mustache.to_html(tmpl, data, partials);
+ });
+ bench.run();
+ $('' + bench.toString() + '
').appendTo(document.body);
+
+ var bench = new Benchmark('compiled', function() {
+ this.compiled_tmpl(data);
+ }, {
+ onStart: function() {
+ this.compiled_tmpl = Mustache.compile(tmpl, partials);
+ }
+ });
+ bench.run();
+ $('' + bench.toString() + '
').appendTo(document.body);
+});
\ No newline at end of file
diff --git a/test/benchmark/benchmark.js b/test/benchmark/benchmark.js
new file mode 100644
index 0000000..06d66c5
--- /dev/null
+++ b/test/benchmark/benchmark.js
@@ -0,0 +1,2221 @@
+/*!
+ * Benchmark.js
+ * Copyright 2010-2011 Mathias Bynens
+ * Based on JSLitmus.js, copyright Robert Kieffer
+ * Modified by John-David Dalton
+ * Available under MIT license
+ */
+(function(window) {
+
+ /** Detect DOM0 timeout API (performed at the bottom) */
+ var HAS_TIMEOUT_API,
+
+ /** Detect Adobe AIR environment */
+ IN_AIR = isClassOf(window.runtime, 'ScriptBridgingProxyObject'),
+
+ /** Detect Java environment */
+ IN_JAVA = isClassOf(window.java, 'JavaPackage'),
+
+ /** Used to integrity check compiled tests */
+ EMBEDDED_UID = +new Date,
+
+ /** Used to skip initialization of the Benchmark constructor */
+ HEADLESS = function() { },
+
+ /** Used to avoid hz of Infinity */
+ CYCLE_DIVISORS = {
+ '1': 4096,
+ '2': 512,
+ '3': 64,
+ '4': 8,
+ '5': 0
+ },
+
+ /**
+ * T-Distribution two-tailed critical values for 95% confidence
+ * http://www.itl.nist.gov/div898/handbook/eda/section3/eda3672.htm
+ */
+ T_DISTRIBUTION = {
+ '1': 12.706,'2': 4.303, '3': 3.182, '4': 2.776, '5': 2.571, '6': 2.447,
+ '7': 2.365, '8': 2.306, '9': 2.262, '10': 2.228, '11': 2.201, '12': 2.179,
+ '13': 2.160, '14': 2.145, '15': 2.131, '16': 2.120, '17': 2.110, '18': 2.101,
+ '19': 2.093, '20': 2.086, '21': 2.080, '22': 2.074, '23': 2.069, '24': 2.064,
+ '25': 2.060, '26': 2.056, '27': 2.052, '28': 2.048, '29': 2.045, '30': 2.042,
+ 'Infinity': 1.960
+ },
+
+ /** Internal cached used by various methods */
+ cache = {
+ 'counter': 0
+ },
+
+ /** Used in Benchmark.hasKey() */
+ hasOwnProperty = cache.hasOwnProperty,
+
+ /** Used to convert array-like objects to arrays */
+ slice = [].slice,
+
+ /** Used generically when invoking over queued arrays */
+ shift = aloClean([].shift),
+
+ /** Math shortcuts */
+ abs = Math.abs,
+ max = Math.max,
+ min = Math.min,
+ pow = Math.pow,
+ sqrt = Math.sqrt;
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Benchmark constructor.
+ * @constructor
+ * @param {String} name A name to identify the benchmark.
+ * @param {Function} fn The test to benchmark.
+ * @param {Object} [options={}] Options object.
+ * @example
+ *
+ * // basic usage
+ * var bench = new Benchmark(fn);
+ *
+ * // or using a name first
+ * var bench = new Benchmark('foo', fn);
+ *
+ * // or with options
+ * var bench = new Benchmark('foo', fn, {
+ *
+ * // displayed by Benchmark#toString if `name` is not available
+ * 'id': 'xyz',
+ *
+ * // called when the benchmark starts running
+ * 'onStart': onStart,
+ *
+ * // called after each run cycle
+ * 'onCycle': onCycle,
+ *
+ * // called when aborted
+ * 'onAbort': onAbort,
+ *
+ * // called when a test errors
+ * 'onError': onError,
+ *
+ * // called when reset
+ * 'onReset': onReset,
+ *
+ * // called when the benchmark completes running
+ * 'onComplete': onComplete,
+ *
+ * // compiled/called before the test loop
+ * 'setup': setup,
+ *
+ * // compiled/called after the test loop
+ * 'teardown': teardown
+ * });
+ *
+ * // or options only
+ * var bench = new Benchmark({
+ *
+ * // benchmark name
+ * 'name': 'foo',
+ *
+ * // benchmark test function
+ * 'fn': fn
+ * });
+ *
+ * // a test's `this` binding is set to the benchmark instance
+ * var bench = new Benchmark('foo', function() {
+ * 'My name is '.concat(this.name); // My name is foo
+ * });
+ */
+ function Benchmark(name, fn, options) {
+ // juggle arguments
+ var me = this;
+ if (isClassOf(name, 'Object')) {
+ // 1 argument
+ options = name;
+ }
+ else if (isClassOf(name, 'Function')) {
+ // 2 arguments
+ options = fn;
+ fn = name;
+ }
+ else {
+ // 3 arguments
+ me.name = name;
+ }
+ // initialize if not headless
+ if (name != HEADLESS) {
+ setOptions(me, options);
+ fn = me.fn || (me.fn = fn);
+ fn.uid || (fn.uid = ++cache.counter);
+
+ me.created = +new Date;
+ me.stats = extend({ }, me.stats);
+ me.times = extend({ }, me.times);
+ }
+ }
+
+ /**
+ * Suite constructor.
+ * @constructor
+ * @member Benchmark
+ * @param {String} name A name to identify the suite.
+ * @param {Object} [options={}] Options object.
+ * @example
+ *
+ * // basic usage
+ * var suite = new Benchmark.Suite;
+ *
+ * // or using a name first
+ * var suite = new Benchmark.Suite('foo');
+ *
+ * // or with options
+ * var suite = new Benchmark.Suite('foo', {
+ *
+ * // called when the suite starts running
+ * 'onStart': onStart,
+ *
+ * // called between running benchmarks
+ * 'onCycle': onCycle,
+ *
+ * // called when aborted
+ * 'onAbort': onAbort,
+ *
+ * // called when a test errors
+ * 'onError': onError,
+ *
+ * // called when reset
+ * 'onReset': onReset,
+ *
+ * // called when the suite completes running
+ * 'onComplete': onComplete
+ * });
+ */
+ function Suite(name, options) {
+ // juggle arguments
+ var me = this;
+ if (isClassOf(name, 'Object')) {
+ options = name;
+ } else {
+ me.name = name;
+ }
+ setOptions(me, options);
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Wraps an array function to ensure array-like-objects (ALO) are empty when their length is `0`.
+ * @private
+ * @param {Function} fn The array function to be wrapped.
+ * @returns {Function} The new function.
+ */
+ function aloClean(fn) {
+ return function() {
+ var me = this,
+ result = fn.apply(me, arguments);
+ // fixes IE<9 and IE8 compatibility mode bugs
+ if (!me.length) {
+ delete me[0];
+ }
+ return result;
+ };
+ }
+
+ /**
+ * Executes a function asynchronously or synchronously.
+ * @private
+ * @param {Object} me The benchmark instance passed to `fn`.
+ * @param {Function} fn Function to be executed.
+ * @param {Boolean} [async=false] Flag to run asynchronously.
+ */
+ function call(me, fn, async) {
+ // only attempt asynchronous calls if supported
+ if (async && HAS_TIMEOUT_API) {
+ me.timerId = setTimeout(function() {
+ delete me.timerId;
+ fn();
+ }, me.CYCLE_DELAY * 1e3);
+ }
+ else {
+ fn();
+ }
+ }
+
+ /**
+ * Clocks the time taken to execute a test per cycle (secs).
+ * @private
+ * @param {Object} me The benchmark instance.
+ * @returns {Number} The time taken.
+ */
+ function clock() {
+ var applet,
+ args,
+ fallback,
+ proto = Benchmark.prototype,
+ timers = [],
+ timerNS = Date,
+ msRes = getRes('ms'),
+ timer = { 'ns': timerNS, 'res': max(0.0015, msRes), 'unit': 'ms' },
+ code = 'var r$,s$,m$=this,i$=m$.count,f$=m$.fn;#{setup}#{start};while(i$--){|}#{end};#{teardown}return{time:r$,uid:"$"}|m$.teardown&&m$.teardown();|f$()|m$.setup&&m$.setup();|n$';
+
+ clock = function(me) {
+ var result,
+ fn = me.fn,
+ compiled = fn.compiled,
+ count = me.count;
+
+ if (applet) {
+ // repair nanosecond timer
+ try {
+ timerNS.nanoTime();
+ } catch(e) {
+ // use non-element to avoid issues with libs that augment them
+ timerNS = new applet.Packages.nano;
+ }
+ }
+ if (compiled == null || compiled) {
+ try {
+ if (!compiled) {
+ // insert test body into the while-loop
+ compiled = createFunction(args,
+ interpolate(code[0], { 'setup': getSource(me.setup) }) +
+ getSource(fn) +
+ interpolate(code[1], { 'teardown': getSource(me.teardown) })
+ );
+ // determine if compiled code is exited early, usually by a rogue
+ // return statement, by checking for a return object with the uid
+ me.count = 1;
+ compiled = fn.compiled = compiled.call(me, timerNS).uid == EMBEDDED_UID && compiled;
+ me.count = count;
+ }
+ if (compiled) {
+ result = compiled.call(me, timerNS).time;
+ }
+ } catch(e) {
+ me.count = count;
+ compiled = fn.compiled = false;
+ }
+ }
+ // fallback to simple while-loop when compiled is false
+ if (!compiled) {
+ result = fallback.call(me, timerNS).time;
+ }
+ return result;
+ };
+
+ function getRes(unit) {
+ var measured,
+ start,
+ count = 30,
+ divisor = 1e3,
+ sample = [];
+
+ // get average smallest measurable time
+ while (count--) {
+ if (unit == 'us') {
+ divisor = 1e6;
+ if (timerNS.stop) {
+ timerNS.start();
+ while(!(measured = timerNS.microseconds()));
+ } else {
+ start = timerNS();
+ while(!(measured = timerNS() - start));
+ }
+ }
+ else if (unit == 'ns') {
+ divisor = 1e9;
+ start = timerNS.nanoTime();
+ while(!(measured = timerNS.nanoTime() - start));
+ }
+ else {
+ start = new timerNS;
+ while(!(measured = new timerNS - start));
+ }
+ // check for broken timers (nanoTime may have issues)
+ // http://alivebutsleepy.srnet.cz/unreliable-system-nanotime/
+ if (measured > 0) {
+ sample.push(measured);
+ } else {
+ sample.push(Infinity);
+ break;
+ }
+ }
+ // convert to seconds
+ return getMean(sample) / divisor;
+ }
+
+ // detect nanosecond support from a Java applet
+ each(window.document && document.applets || [], function(element) {
+ if (timerNS = applet = 'nanoTime' in element && element) {
+ return false;
+ }
+ });
+
+ // or the exposed Java API
+ // http://download.oracle.com/javase/6/docs/api/java/lang/System.html#nanoTime()
+ try {
+ timerNS = java.lang.System;
+ } catch(e) { }
+
+ // check type in case Safari returns an object instead of a number
+ try {
+ if (typeof timerNS.nanoTime() == 'number') {
+ timers.push({ 'ns': timerNS, 'res': getRes('ns'), 'unit': 'ns' });
+ }
+ } catch(e) { }
+
+ // detect Chrome's microsecond timer:
+ // enable benchmarking via the --enable-benchmarking command
+ // line switch in at least Chrome 7 to use chrome.Interval
+ try {
+ if (timerNS = new (window.chrome || window.chromium).Interval) {
+ timers.push({ 'ns': timerNS, 'res': getRes('us'), 'unit': 'us' });
+ }
+ } catch(e) { }
+
+ // detect Node's microtime module:
+ // npm install microtime
+ try {
+ if (timerNS = typeof require == 'function' && !window.require && require('microtime').now) {
+ timers.push({ 'ns': timerNS, 'res': getRes('us'), 'unit': 'us' });
+ }
+ } catch(e) { }
+
+ // pick timer with highest resolution
+ timerNS = (timer = reduce(timers, function(timer, other) {
+ return other.res < timer.res ? other : timer;
+ }, timer)).ns;
+
+ // restore ms res
+ if (timer.unit == 'ms') {
+ timer.res = msRes;
+ }
+ // remove unused applet
+ if (timer.unit != 'ns' && applet) {
+ applet.parentNode.removeChild(applet);
+ applet = null;
+ }
+ // error if there are no working timers
+ if (timer.res == Infinity) {
+ throw new Error('Benchmark.js was unable to find a working timer.');
+ }
+ // use API of chosen timer
+ if (timer.unit == 'ns') {
+ code = interpolate(code, {
+ 'start': 's$=n$.nanoTime()',
+ 'end': 'r$=(n$.nanoTime()-s$)/1e9'
+ });
+ }
+ else if (timer.unit == 'us') {
+ code = interpolate(code, timerNS.stop ? {
+ 'start': 's$=n$.start()',
+ 'end': 'r$=n$.microseconds()/1e6'
+ } : {
+ 'start': 's$=n$()',
+ 'end': 'r$=(n$()-s$)/1e6'
+ });
+ }
+ else {
+ code = interpolate(code, {
+ 'start': 's$=new n$',
+ 'end': 'r$=(new n$-s$)/1e3'
+ });
+ }
+
+ // inject uid into variable names to avoid collisions with
+ // embedded tests and create non-embedding fallback
+ code = code.replace(/\$/g, EMBEDDED_UID).split('|');
+ args = code.pop();
+ fallback = createFunction(args,
+ interpolate(code[0], { 'setup': code.pop() }) +
+ code.pop() +
+ interpolate(code[1], { 'teardown': code.pop() })
+ );
+
+ // resolve time to achieve a percent uncertainty of 1%
+ proto.MIN_TIME || (proto.MIN_TIME = max(timer.res / 2 / 0.01, 0.05));
+ return clock.apply(null, arguments);
+ }
+
+ /**
+ * Creates a function from the given arguments string and body.
+ * @private
+ * @param {String} args The comma separated function arguments.
+ * @param {String} body The function body.
+ * @returns {Function} The new function.
+ */
+ function createFunction() {
+ var scripts,
+ prop = 'c' + EMBEDDED_UID;
+
+ createFunction = function(args, body) {
+ var parent = scripts[0].parentNode,
+ script = document.createElement('script');
+
+ script.appendChild(document.createTextNode('Benchmark.' + prop + '=function(' + args + '){' + body + '}'));
+ parent.removeChild(parent.insertBefore(script, scripts[0]));
+ return [Benchmark[prop], delete Benchmark[prop]][0];
+ };
+
+ // fix JaegerMonkey bug
+ // http://bugzil.la/639720
+ try {
+ scripts = document.getElementsByTagName('script');
+ createFunction = createFunction('', 'return ' + EMBEDDED_UID)() == EMBEDDED_UID && createFunction;
+ } catch (e) {
+ createFunction = false;
+ }
+ createFunction || (createFunction = Function);
+ return createFunction.apply(null, arguments);
+ }
+
+ /**
+ * Wraps a function and passes `this` to the original function as the first argument.
+ * @private
+ * @param {Function} fn The function to be wrapped.
+ * @returns {Function} The new function.
+ */
+ function methodize(fn) {
+ return function() {
+ return fn.apply(this, [this].concat(slice.call(arguments)));
+ };
+ }
+
+ /**
+ * Gets the critical value for the specified degrees of freedom.
+ * @private
+ * @param {Number} df The degrees of freedom.
+ * @returns {Number} The critical value.
+ */
+ function getCriticalValue(df) {
+ return T_DISTRIBUTION[Math.round(df) || 1] || T_DISTRIBUTION.Infinity;
+ }
+
+ /**
+ * Computes the arithmetic mean of a sample.
+ * @private
+ * @param {Array} sample The sample.
+ * @returns {Number} The mean.
+ */
+ function getMean(sample) {
+ return reduce(sample, function(sum, x) {
+ return sum + x;
+ }, 0) / sample.length || 0;
+ }
+
+ /**
+ * Gets the source code of a function.
+ * @private
+ * @param {Function} fn The function.
+ * @returns {String} The function's source code.
+ */
+ function getSource(fn) {
+ return trim((/^[^{]+{([\s\S]*)}\s*$/.exec(fn) || 0)[1] || '')
+ .replace(/([^\n])$/, '$1\n');
+ }
+
+ /**
+ * Sets the options of a benchmark.
+ * @private
+ * @param {Object} me The benchmark instance.
+ * @param {Object} [options={}] Options object.
+ */
+ function setOptions(me, options) {
+ options = extend(extend({ }, me.constructor.options), options);
+ me.options = each(options, function(value, key) {
+ // add event listeners
+ if (/^on[A-Z]/.test(key)) {
+ each(key.split(' '), function(key) {
+ me.on(key.slice(2).toLowerCase(), value);
+ });
+ } else {
+ me[key] = value;
+ }
+ });
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * A bare-bones `Array#forEach`/`for-in` own property solution.
+ * Callbacks may terminate the loop by explicitly returning `false`.
+ * @static
+ * @member Benchmark
+ * @param {Array|Object} object The object to iterate over.
+ * @param {Function} callback The function called per iteration.
+ * @returns {Array|Object} Returns the object iterated over.
+ */
+ function each(object, callback) {
+ var i = -1,
+ length = object.length;
+
+ if (hasKey(object, 'length') && length > -1 && length < 4294967296) {
+ while (++i < length) {
+ if (i in object && callback(object[i], i, object) === false) {
+ break;
+ }
+ }
+ } else {
+ for (i in object) {
+ if (hasKey(object, i) && callback(object[i], i, object) === false) {
+ break;
+ }
+ }
+ }
+ return object;
+ }
+
+ /**
+ * Copies own/inherited properties of a source object to the destination object.
+ * @static
+ * @member Benchmark
+ * @param {Object} destination The destination object.
+ * @param {Object} [source={}] The source object.
+ * @returns {Object} The destination object.
+ */
+ function extend(destination, source) {
+ source || (source = { });
+ for (var key in source) {
+ destination[key] = source[key];
+ }
+ return destination;
+ }
+
+ /**
+ * A generic bare-bones `Array#filter` solution.
+ * @static
+ * @member Benchmark
+ * @param {Array} array The array to iterate over.
+ * @param {Function|String} callback The function/alias called per iteration.
+ * @returns {Array} A new array of values that passed callback filter.
+ * @example
+ *
+ * // get odd numbers
+ * Benchmark.filter([1, 2, 3, 4, 5], function(n) {
+ * return n % 2;
+ * }); // -> [1, 3, 5];
+ *
+ * // get fastest benchmarks
+ * Benchmark.filter(benches, 'fastest');
+ *
+ * // get slowest benchmarks
+ * Benchmark.filter(benches, 'slowest');
+ *
+ * // get benchmarks that completed without erroring
+ * Benchmark.filter(benches, 'successful');
+ */
+ function filter(array, callback) {
+ var result;
+ if (callback == 'successful') {
+ // callback to exclude errored or unrun benchmarks
+ callback = function(bench) { return bench.cycles; };
+ }
+ else if (/^(?:fast|slow)est$/.test(callback)) {
+ // get successful, sort by period + margin of error, and filter fastest/slowest
+ result = filter(array, 'successful').sort(function(a, b) {
+ a = a.stats;
+ b = b.stats;
+ return (a.mean + a.ME > b.mean + b.ME ? 1 : -1) * (callback == 'fastest' ? 1 : -1);
+ });
+ result = filter(result, function(bench) {
+ return !result[0].compare(bench);
+ });
+ }
+ return result || reduce(array, function(result, value, index) {
+ return callback(value, index, array) ? result.push(value) && result : result;
+ }, []);
+ }
+
+ /**
+ * Converts a number to a more readable comma-separated string representation.
+ * @static
+ * @member Benchmark
+ * @param {Number} number The number to convert.
+ * @returns {String} The more readable string representation.
+ */
+ function formatNumber(number) {
+ number = String(number).split('.');
+ return number[0].replace(/(?=(?:\d{3})+$)(?!\b)/g, ',') + (number[1] ? '.' + number[1] : '');
+ }
+
+ /**
+ * Checks if an object has the specified key as a direct property.
+ * @static
+ * @member Benchmark
+ * @param {Object} object The object to check.
+ * @param {String} key The key to check for.
+ * @returns {Boolean} Returns `true` if key is a direct property, else `false`.
+ */
+ function hasKey(object, key) {
+ var result,
+ parent = (object.constructor || Object).prototype;
+
+ // for modern browsers
+ object = Object(object);
+ if (isClassOf(hasOwnProperty, 'Function')) {
+ result = hasOwnProperty.call(object, key);
+ }
+ // for Safari 2
+ else if (cache.__proto__ == Object.prototype) {
+ object.__proto__ = [object.__proto__, object.__proto__ = null, result = key in object][0];
+ }
+ // for others (not as accurate)
+ else {
+ result = key in object && !(key in parent && object[key] === parent[key]);
+ }
+ return result;
+ }
+
+ /**
+ * A generic bare-bones `Array#indexOf` solution.
+ * @static
+ * @member Benchmark
+ * @param {Array} array The array to iterate over.
+ * @param {Mixed} value The value to search for.
+ * @returns {Number} The index of the matched value or `-1`.
+ */
+ function indexOf(array, value) {
+ var result = -1;
+ each(array, function(v, i) {
+ if (v === value) {
+ result = i;
+ return false;
+ }
+ });
+ return result;
+ }
+
+ /**
+ * Invokes a method on all items in an array.
+ * @static
+ * @member Benchmark
+ * @param {Array} benches Array of benchmarks to iterate over.
+ * @param {String|Object} name The name of the method to invoke OR options object.
+ * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the method with.
+ * @returns {Array} A new array of values returned from each method invoked.
+ * @example
+ *
+ * // invoke `reset` on all benchmarks
+ * Benchmark.invoke(benches, 'reset');
+ *
+ * // invoke `emit` with arguments
+ * Benchmark.invoke(benches, 'emit', 'complete', listener);
+ *
+ * // invoke `run(true)`, treat benchmarks as a queue, and register invoke callbacks
+ * Benchmark.invoke(benches, {
+ *
+ * // invoke the `run` method
+ * 'name': 'run',
+ *
+ * // pass a single argument
+ * 'args': true,
+ *
+ * // treat as queue, removing benchmarks from front of `benches` until empty
+ * 'queued': true,
+ *
+ * // called before any benchmarks have been invoked.
+ * 'onStart': onStart,
+ *
+ * // called between invoking benchmarks
+ * 'onCycle': onCycle,
+ *
+ * // called after all benchmarks have been invoked.
+ * 'onComplete': onComplete
+ * });
+ */
+ function invoke(benches, name) {
+ var args,
+ async,
+ bench,
+ queued,
+ i = 0,
+ options = { 'onStart': noop, 'onCycle': noop, 'onComplete': noop },
+ result = slice.call(benches, 0);
+
+ function execute() {
+ var listeners;
+ if (async) {
+ // use `next` as a listener
+ bench.on('complete', next);
+ listeners = bench.events['complete'];
+ listeners.splice(0, 0, listeners.pop());
+ }
+ // execute method
+ result[i] = bench[name].apply(bench, args);
+ // if synchronous return true until finished
+ return async || next();
+ }
+
+ function next() {
+ var last = bench;
+ bench = false;
+
+ if (async) {
+ last.removeListener('complete', next);
+ last.emit('complete');
+ }
+ // choose next benchmark if not exiting early
+ if (options.onCycle.call(benches, last) !== false) {
+ if (queued) {
+ // use generic shift
+ shift.call(benches);
+ bench = benches[0];
+ } else {
+ bench = benches[++i];
+ }
+ }
+ if (bench) {
+ if (async) {
+ call(bench, execute, async);
+ } else {
+ return true;
+ }
+ } else {
+ options.onComplete.call(benches, last);
+ }
+ // when async the `return false` will cancel the rest of the "complete"
+ // listeners because they were called above and when synchronous it will
+ // end the while-loop
+ return false;
+ }
+
+ // juggle arguments
+ if (isClassOf(name, 'String')) {
+ args = slice.call(arguments, 2);
+ } else {
+ options = extend(options, name);
+ name = options.name;
+ args = isArray(args = 'args' in options ? options.args : []) ? args : [args];
+ queued = options.queued;
+ }
+ // async for use with Benchmark#run only
+ if (name == 'run') {
+ async = (args[0] == null ? Benchmark.prototype.DEFAULT_ASYNC :
+ args[0]) && HAS_TIMEOUT_API;
+ }
+ // start iterating over the array
+ if (bench = benches[0]) {
+ options.onStart.call(benches, bench);
+ if (async) {
+ call(bench, execute, async);
+ } else {
+ result.length = 0;
+ while (execute());
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Modify a string by replacing named tokens with matching object property values.
+ * @static
+ * @member Benchmark
+ * @param {String} string The string to modify.
+ * @param {Object} object The template object.
+ * @returns {String} The modified string.
+ * @example
+ *
+ * Benchmark.interpolate('#{greet} #{who}!', {
+ * 'greet': 'Hello',
+ * 'who': 'world'
+ * }); // -> 'Hello world!'
+ */
+ function interpolate(string, object) {
+ string = string == null ? '' : string;
+ each(object || { }, function(value, key) {
+ string = string.replace(RegExp('#\\{' + key + '\\}', 'g'), String(value));
+ });
+ return string;
+ }
+
+ /**
+ * Determines if the given value is an array.
+ * @static
+ * @member Benchmark
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if value is an array, else `false`.
+ */
+ function isArray(value) {
+ return isClassOf(value, 'Array');
+ }
+
+ /**
+ * Checks if an object is of the specified class.
+ * @static
+ * @member Benchmark
+ * @param {Object} object The object.
+ * @param {String} name The name of the class.
+ * @returns {Boolean} Returns `true` if of the class, else `false`.
+ */
+ function isClassOf(object, name) {
+ return object != null && {}.toString.call(object).slice(8, -1) == name;
+ }
+
+ /**
+ * Host objects can return type values that are different from their actual
+ * data type. The objects we are concerned with usually return non-primitive
+ * types of object, function, or unknown.
+ * @static
+ * @member Benchmark
+ * @param {Mixed} object The owner of the property.
+ * @param {String} property The property to check.
+ * @returns {Boolean} Returns `true` if the property value is a non-primitive, else `false`.
+ */
+ function isHostType(object, property) {
+ return !/^(?:boolean|number|string|undefined)$/
+ .test(typeof object[property]) && !!object[property];
+ }
+
+ /**
+ * Creates a string of joined array values or object key-value pairs.
+ * @static
+ * @member Benchmark
+ * @param {Array|Object} object The object to operate on.
+ * @param {String} [separator1=','] The separator used between key-value pairs.
+ * @param {String} [separator2=': '] The separator used between keys and values.
+ * @returns {String} The joined result.
+ */
+ function join(object, separator1, separator2) {
+ var pairs = [];
+ if (isArray(object)) {
+ pairs = object;
+ }
+ else {
+ separator2 || (separator2 = ': ');
+ each(object, function(value, key) {
+ pairs.push(key + separator2 + value);
+ });
+ }
+ return pairs.join(separator1 || ',');
+ }
+
+ /**
+ * A generic bare-bones `Array#map` solution.
+ * @static
+ * @member Benchmark
+ * @param {Array} array The array to iterate over.
+ * @param {Function} callback The function called per iteration.
+ * @returns {Array} A new array of values returned by the callback.
+ */
+ function map(array, callback) {
+ return reduce(array, function(result, value, index) {
+ result.push(callback(value, index, array));
+ return result;
+ }, []);
+ }
+
+ /**
+ * A no-operation function.
+ * @static
+ * @member Benchmark
+ */
+ function noop() {
+ // no operation performed
+ }
+
+ /**
+ * Retrieves the value of a specified property from all items in an array.
+ * @static
+ * @member Benchmark
+ * @param {Array} array The array to iterate over.
+ * @param {String} property The property to pluck.
+ * @returns {Array} A new array of property values.
+ */
+ function pluck(array, property) {
+ return map(array, function(object) {
+ return object[property];
+ });
+ }
+
+ /**
+ * A generic bare-bones `Array#reduce` solution.
+ * @static
+ * @member Benchmark
+ * @param {Array} array The array to iterate over.
+ * @param {Function} callback The function called per iteration.
+ * @param {Mixed} accumulator Initial value of the accumulator.
+ * @returns {Mixed} The accumulator.
+ */
+ function reduce(array, callback, accumulator) {
+ each(array, function(value, index) {
+ accumulator = callback(accumulator, value, index, array);
+ });
+ return accumulator;
+ }
+
+ /**
+ * A generic bare-bones `String#trim` solution.
+ * @static
+ * @member Benchmark
+ * @param {String} string The string to trim.
+ * @returns {String} The trimmed string.
+ */
+ function trim(string) {
+ return String(string).replace(/^\s+/, '').replace(/\s+$/, '');
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Aborts all benchmarks in the suite.
+ * @name abort
+ * @member Benchmark.Suite
+ * @returns {Object} The suite instance.
+ */
+ function abortSuite() {
+ var me = this;
+ me.aborted = true;
+ me.running = false;
+ invoke(me, 'abort');
+ me.emit('abort');
+ return me;
+ }
+
+ /**
+ * Adds a test to the benchmark suite.
+ * @member Benchmark.Suite
+ * @param {String} name A name to identify the benchmark.
+ * @param {Function} fn The test to benchmark.
+ * @param {Object} [options={}] Options object.
+ * @returns {Object} The benchmark instance.
+ * @example
+ *
+ * // basic usage
+ * suite.add(fn);
+ *
+ * // or using a name first
+ * suite.add('foo', fn);
+ *
+ * // or with options
+ * suite.add('foo', fn, {
+ * 'onCycle': onCycle,
+ * 'onComplete': onComplete
+ * });
+ */
+ function add(name, fn, options) {
+ var me = this,
+ bench = new Benchmark(HEADLESS);
+
+ Benchmark.call(bench, name, fn, options);
+ me.push(bench);
+ me.emit('add', bench);
+ return me;
+ }
+
+ /**
+ * Creates a new suite with cloned benchmarks.
+ * @name clone
+ * @member Benchmark.Suite
+ * @param {Object} options Options object to overwrite cloned options.
+ * @returns {Object} The new suite instance.
+ */
+ function cloneSuite(options) {
+ var me = this,
+ result = new me.constructor(extend(extend({ }, me.options), options));
+ // copy own properties
+ each(me, function(value, key) {
+ if (!hasKey(result, key)) {
+ result[key] = /^\d+$/.test(key) ? value.clone() : value;
+ }
+ });
+ return result.reset();
+ }
+
+ /**
+ * A bare-bones `Array#filter` solution.
+ * @name filter
+ * @member Benchmark.Suite
+ * @param {Function|String} callback The function/alias called per iteration.
+ * @returns {Object} A new suite of benchmarks that passed callback filter.
+ */
+ function filterSuite(callback) {
+ var me = this,
+ result = new me.constructor;
+
+ result.push.apply(result, filter(me, callback));
+ return result;
+ }
+
+ /**
+ * Resets all benchmarks in the suite.
+ * @name reset
+ * @member Benchmark.Suite
+ * @returns {Object} The suite instance.
+ */
+ function resetSuite() {
+ var me = this;
+ me.aborted = me.running = false;
+ invoke(me, 'reset');
+ me.emit('reset');
+ return me;
+ }
+
+ /**
+ * Runs the suite.
+ * @name run
+ * @member Benchmark.Suite
+ * @param {Boolean} [async=false] Flag to run asynchronously.
+ * @param {Boolean} [queued=false] Flag to treat benchmarks as a queue.
+ * @returns {Object} The suite instance.
+ */
+ function runSuite(async, queued) {
+ var me = this;
+ me.reset();
+ me.running = true;
+ invoke(me, {
+ 'name': 'run',
+ 'args': async,
+ 'queued': queued,
+ 'onStart': function(bench) {
+ me.emit('start', bench);
+ },
+ 'onCycle': function(bench) {
+ if (bench.error) {
+ me.emit('error', bench);
+ }
+ return !me.aborted && me.emit('cycle', bench);
+ },
+ 'onComplete': function(bench) {
+ me.running = false;
+ me.emit('complete', bench);
+ }
+ });
+ return me;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Registers a single listener of a specified event type.
+ * @member Benchmark, Benchmark.Suite
+ * @param {String} type The event type.
+ * @param {Function} listener The function called when the event occurs.
+ * @returns {Object} The benchmark instance.
+ * @example
+ *
+ * // basic usage
+ * bench.addListener('cycle', listener);
+ *
+ * // register a listener for multiple event types
+ * bench.addListener('start cycle', listener);
+ */
+ function addListener(type, listener) {
+ var me = this,
+ events = me.events || (me.events = { });
+
+ each(type.split(' '), function(type) {
+ (events[type] || (events[type] = [])).push(listener);
+ });
+ return me;
+ }
+
+ /**
+ * Executes all registered listeners of a specified event type.
+ * @member Benchmark, Benchmark.Suite
+ * @param {String} type The event type.
+ */
+ function emit(type) {
+ var me = this,
+ args = slice.call(arguments, 1),
+ events = me.events,
+ listeners = events && events[type] || [],
+ successful = true;
+
+ each(listeners, function(listener) {
+ if (listener.apply(me, args) === false) {
+ successful = false;
+ return successful;
+ }
+ });
+ return successful;
+ }
+
+ /**
+ * Unregisters a single listener of a specified event type.
+ * @member Benchmark, Benchmark.Suite
+ * @param {String} type The event type.
+ * @param {Function} listener The function to unregister.
+ * @returns {Object} The benchmark instance.
+ * @example
+ *
+ * // basic usage
+ * bench.removeListener('cycle', listener);
+ *
+ * // unregister a listener for multiple event types
+ * bench.removeListener('start cycle', listener);
+ */
+ function removeListener(type, listener) {
+ var me = this,
+ events = me.events;
+
+ each(type.split(' '), function(type) {
+ var listeners = events && events[type] || [],
+ index = indexOf(listeners, listener);
+ if (index > -1) {
+ listeners.splice(index, 1);
+ }
+ });
+ return me;
+ }
+
+ /**
+ * Unregisters all listeners of a specified event type.
+ * @member Benchmark, Benchmark.Suite
+ * @param {String} type The event type.
+ * @returns {Object} The benchmark instance.
+ * @example
+ *
+ * // basic usage
+ * bench.removeAllListeners('cycle');
+ *
+ * // unregister all listeners for multiple event types
+ * bench.removeListener('start cycle');
+ */
+ function removeAllListeners(type) {
+ var me = this,
+ events = me.events;
+
+ each(type.split(' '), function(type) {
+ (events && events[type] || []).length = 0;
+ });
+ return me;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Aborts the benchmark without recording times.
+ * @member Benchmark
+ * @returns {Object} The benchmark instance.
+ */
+ function abort() {
+ var me = this;
+ if (me.running) {
+ if (me.timerId && HAS_TIMEOUT_API) {
+ clearTimeout(me.timerId);
+ delete me.timerId;
+ }
+ // set running as NaN so reset() will detect it as falsey and *not* call abort(),
+ // but *will* detect it as a change and fire the onReset() callback
+ me.running = NaN;
+ me.reset();
+ me.aborted = true;
+ me.emit('abort');
+ }
+ return me;
+ }
+
+ /**
+ * Creates a new benchmark using the same test and options.
+ * @member Benchmark
+ * @param {Object} options Options object to overwrite cloned options.
+ * @returns {Object} The new benchmark instance.
+ * @example
+ *
+ * var bizarro = bench.clone({
+ * 'name': 'doppelganger'
+ * });
+ */
+ function clone(options) {
+ var me = this,
+ result = new me.constructor(me.fn, extend(extend({ }, me.options), options));
+ // copy own properties
+ each(me, function(value, key) {
+ if (!hasKey(result, key)) {
+ result[key] = value;
+ }
+ });
+ return result.reset();
+ }
+
+ /**
+ * Determines if the benchmark's period is smaller than another.
+ * @member Benchmark
+ * @param {Object} other The benchmark to compare.
+ * @returns {Number} Returns `1` if smaller, `-1` if larger, and `0` if indeterminate.
+ */
+ function compare(other) {
+ // unpaired two-sample t-test assuming equal variance
+ // http://en.wikipedia.org/wiki/Student's_t-test
+ // http://www.chem.utoronto.ca/coursenotes/analsci/StatsTutorial/12tailed.html
+ var a = this.stats,
+ b = other.stats,
+ df = a.size + b.size - 2,
+ pooled = (((a.size - 1) * a.variance) + ((b.size - 1) * b.variance)) / df,
+ tstat = (a.mean - b.mean) / sqrt(pooled * (1 / a.size + 1 / b.size)),
+ near = abs(1 - a.mean / b.mean) < 0.055 && a.RME < 3 && b.RME < 3;
+
+ // check if the means aren't close and the t-statistic is significant
+ return !near && abs(tstat) > getCriticalValue(df) ? (tstat > 0 ? -1 : 1) : 0;
+ }
+
+ /**
+ * Reset properties and abort if running.
+ * @member Benchmark
+ * @returns {Object} The benchmark instance.
+ */
+ function reset() {
+ var changed,
+ pair,
+ me = this,
+ source = extend(extend({ }, me.constructor.prototype), me.options),
+ pairs = [[source, me]];
+
+ function check(value, key) {
+ var other = pair[1][key];
+ if (value && isClassOf(value, 'Object')) {
+ pairs.push([value, other]);
+ }
+ else if (!isClassOf(value, 'Function') &&
+ key != 'created' && value != other) {
+ pair[1][key] = value;
+ changed = true;
+ }
+ }
+
+ if (me.running) {
+ // no worries, reset() is called within abort()
+ me.abort();
+ me.aborted = source.aborted;
+ }
+ else {
+ // check if properties have changed and reset them
+ while (pairs.length) {
+ each((pair = pairs.pop(), pair[0]), check);
+ }
+ if (changed) {
+ me.emit('reset');
+ }
+ }
+ return me;
+ }
+
+ /**
+ * Displays relevant benchmark information when coerced to a string.
+ * @member Benchmark
+ * @returns {String} A string representation of the benchmark instance.
+ */
+ function toString() {
+ var me = this,
+ error = me.error,
+ hz = me.hz,
+ stats = me.stats,
+ size = stats.size,
+ pm = IN_JAVA ? '+/-' : '\xb1',
+ result = me.name || me.id || ('');
+
+ if (error) {
+ result += ': ' + join(error);
+ } else {
+ result += ' x ' + formatNumber(hz.toFixed(hz < 100 ? 2 : 0)) + ' ops/sec ' + pm +
+ stats.RME.toFixed(2) + '% (' + size + ' run' + (size == 1 ? '' : 's') + ' sampled)';
+ }
+ return result;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Computes stats on benchmark results.
+ * @private
+ * @param {Object} me The benchmark instance.
+ * @param {Boolean} [async=false] Flag to run asynchronously.
+ */
+ function compute(me, async) {
+ var queue = [],
+ sample = [],
+ runCount = me.INIT_RUN_COUNT;
+
+ function enqueue(count) {
+ while (count--) {
+ queue.push(me.clone({
+ 'computing': me,
+ 'events': { 'start': [update], 'cycle': [update] }
+ }));
+ }
+ }
+
+ function update() {
+ // port changes from clone to host
+ var clone = this;
+ if (me.running) {
+ if (clone.cycles) {
+ // the me.count and me.cycles props of the host are updated in cycle() below
+ me.hz = clone.hz;
+ me.times.period = clone.times.period;
+ me.INIT_RUN_COUNT = clone.INIT_RUN_COUNT;
+ me.emit('cycle');
+ }
+ else if (clone.error) {
+ me.abort();
+ me.error = clone.error;
+ me.emit('error');
+ }
+ else {
+ // on start
+ clone.count = me.INIT_RUN_COUNT;
+ }
+ } else if (me.aborted) {
+ clone.abort();
+ }
+ }
+
+ function evaluate(clone) {
+ var mean,
+ moe,
+ rme,
+ sd,
+ sem,
+ variance,
+ now = +new Date,
+ times = me.times,
+ aborted = me.aborted,
+ elapsed = (now - times.start) / 1e3,
+ maxedOut = elapsed > me.MAX_TIME_ELAPSED,
+ size = sample.push(clone.times.period),
+ varOf = function(sum, x) { return sum + pow(x - mean, 2); };
+
+ // exit early for aborted or unclockable tests
+ if (aborted || clone.hz == Infinity) {
+ maxedOut = !(size = sample.length = queue.length = 0);
+ }
+ // simulate onComplete and enqueue additional runs if needed
+ if (queue.length < 2) {
+ // sample mean (estimate of the population mean)
+ mean = getMean(sample);
+ // sample variance (estimate of the population variance)
+ variance = reduce(sample, varOf, 0) / (size - 1);
+ // sample standard deviation (estimate of the population standard deviation)
+ sd = sqrt(variance);
+ // standard error of the mean (aka the standard deviation of the sampling distribution of the sample mean)
+ sem = sd / sqrt(size);
+ // margin of error
+ moe = sem * getCriticalValue(size - 1);
+ // relative margin of error
+ rme = (moe / mean) * 100 || 0;
+
+ // if time permits, increase sample size to reduce the margin of error
+ if (!maxedOut) {
+ enqueue(1);
+ }
+ else {
+ // set host values
+ if (!aborted) {
+ me.running = false;
+ times.stop = now;
+ times.elapsed = elapsed;
+ extend(me.stats, {
+ 'ME': moe,
+ 'RME': rme,
+ 'SEM': sem,
+ 'deviation': sd,
+ 'mean': mean,
+ 'size': size,
+ 'variance': variance
+ });
+
+ if (me.hz != Infinity) {
+ times.period = mean;
+ times.cycle = mean * me.count;
+ me.hz = 1 / mean;
+ }
+ }
+ me.INIT_RUN_COUNT = runCount;
+ me.emit('complete');
+ }
+ }
+ return !aborted;
+ }
+
+ // init sample/queue and begin
+ enqueue(me.MIN_SAMPLE_SIZE);
+ invoke(queue, { 'name': 'run', 'args': async, 'queued': true, 'onCycle': evaluate });
+ }
+
+ /**
+ * Runs the benchmark.
+ * @member Benchmark
+ * @param {Boolean} [async=false] Flag to run asynchronously.
+ * @returns {Object} The benchmark instance.
+ */
+ function run(async) {
+ var me = this;
+
+ function cycle() {
+ var clocked,
+ divisor,
+ minTime,
+ period,
+ count = me.count,
+ host = me.computing,
+ times = me.times;
+
+ // continue, if not aborted between cycles
+ if (me.running) {
+ me.cycles++;
+ host.cycles++;
+ host.count = count;
+
+ try {
+ clocked = clock(host);
+ minTime = me.MIN_TIME;
+ } catch(e) {
+ me.abort();
+ me.error = e;
+ me.emit('error');
+ }
+ }
+ // continue, if not errored
+ if (me.running) {
+ // time taken to complete last test cycle
+ times.cycle = clocked;
+ // seconds per operation
+ period = times.period = clocked / count;
+ // ops per second
+ me.hz = 1 / period;
+ // do we need to do another cycle?
+ me.running = clocked < minTime;
+ // avoid working our way up to this next time
+ me.INIT_RUN_COUNT = count;
+
+ if (me.running) {
+ // tests may clock at 0 when INIT_RUN_COUNT is a small number,
+ // to avoid that we set its count to something a bit higher
+ if (!clocked && (divisor = CYCLE_DIVISORS[me.cycles]) != null) {
+ count = Math.floor(4e6 / divisor);
+ }
+ // calculate how many more iterations it will take to achive the MIN_TIME
+ if (count <= me.count) {
+ count += Math.ceil((minTime - clocked) / period);
+ }
+ me.running = count != Infinity;
+ }
+ }
+ // should we exit early?
+ if (me.emit('cycle') === false) {
+ me.abort();
+ }
+ // figure out what to do next
+ if (me.running) {
+ me.count = count;
+ call(me, cycle, async);
+ } else {
+ // fix TraceMonkey bug
+ // http://bugzil.la/509069
+ if (window.Benchmark == Benchmark) {
+ window.Benchmark = 1;
+ window.Benchmark = Benchmark;
+ }
+ me.emit('complete');
+ }
+ }
+
+ // set running to false so reset() won't call abort()
+ me.running = false;
+ me.reset();
+ me.running = true;
+
+ me.count = me.INIT_RUN_COUNT;
+ me.times.start = +new Date;
+ me.emit('start');
+
+ async = (async == null ? me.DEFAULT_ASYNC : async) && HAS_TIMEOUT_API;
+ if (me.computing) {
+ cycle();
+ } else {
+ compute(me, async);
+ }
+ return me;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Platform object containing browser name, version, and operating system.
+ * @static
+ * @member Benchmark
+ * @type Object
+ */
+ Benchmark.platform = (function() {
+ var me = this,
+ alpha = IN_JAVA ? 'a' : '\u03b1',
+ beta = IN_JAVA ? 'b' : '\u03b2',
+ description = [],
+ doc = window.document || {},
+ nav = window.navigator || {},
+ ua = nav.userAgent || 'unknown platform',
+ layout = /Gecko|Trident|WebKit/.exec(ua),
+ data = { '6.1': 'Server 2008 R2 / 7', '6.0': 'Server 2008 / Vista', '5.2': 'Server 2003 / XP x64', '5.1': 'XP', '5.0': '2000', '4.0': 'NT', '4.9': 'ME' },
+ name = 'Avant Browser,Camino,Epiphany,Fennec,Flock,Galeon,GreenBrowser,iCab,Iron,K-Meleon,Konqueror,Lunascape,Maxthon,Minefield,Nook Browser,RockMelt,SeaMonkey,Sleipnir,SlimBrowser,Sunrise,Swiftfox,Opera Mini,Opera,Chrome,Firefox,IE,Safari',
+ os = 'Android,Cygwin,SymbianOS,webOS[ /]\\d,Linux,Mac OS(?: X)?,Macintosh,Windows 98;,Windows ',
+ product = 'BlackBerry\\s?\\d+,iP[ao]d,iPhone,Kindle,Nokia,Nook,PlayBook,Samsung,Xoom',
+ version = isClassOf(window.opera, 'Opera') && opera.version();
+
+ function format(string) {
+ // trim and conditionally capitalize
+ return /^(?:webOS|i(?:OS|P))/.test(string = trim(string)) ? string :
+ string.charAt(0).toUpperCase() + string.slice(1);
+ }
+
+ name = reduce(name.split(','), function(name, guess) {
+ return name || RegExp(guess + '\\b', 'i').exec(ua) && guess;
+ });
+
+ product = reduce(product.split(','), function(product, guess) {
+ if (!product && (product = RegExp(guess + '[^ ();-]*', 'i').exec(ua))) {
+ // correct character case and split by forward slash
+ if ((product = String(product).replace(RegExp(guess = /\w+/.exec(guess), 'i'), guess).split('/'))[1]) {
+ if (/[\d.]+/.test(product[0])) {
+ version = version || product[1];
+ } else {
+ product[0] += ' ' + product[1];
+ }
+ }
+ product = format(product[0].replace(/([a-z])(\d)/i, '$1 $2').split('-')[0]);
+ }
+ return product;
+ });
+
+ os = reduce(os.split(','), function(os, guess) {
+ if (!os && (os = RegExp(guess + '[^();/-]*').exec(ua))) {
+ // platform tokens defined at
+ // http://msdn.microsoft.com/en-us/library/ms537503(VS.85).aspx
+ if (/^win/i.test(os) && (data = data[0/*opera fix*/,/[456]\.\d/.exec(os)])) {
+ os = 'Windows ' + data;
+ }
+ // normalize iOS
+ else if (/^iP/.test(product)) {
+ name || (name = 'Safari');
+ os = 'iOS' + ((data = /\bOS ([\d_]+)/.exec(ua)) ? ' ' + data[1] : '');
+ }
+ // cleanup
+ os = String(os).replace(RegExp(guess = /\w+/.exec(guess), 'i'), guess)
+ .replace(/\/(\d)/, ' $1').replace(/_/g, '.').replace(/x86\.64/g, 'x86_64')
+ .replace('Macintosh', 'Mac OS').replace(/(OS X) Mach$/, '$1').split(' on ')[0];
+ }
+ return os;
+ });
+
+ // detect simulator
+ if (/Simulator/i.exec(ua)) {
+ product = (product ? product + ' ' : '') + 'Simulator';
+ }
+ // detect non Firefox Gecko/Safari WebKit based browsers
+ if (ua && (data = /^(?:Firefox|Safari|null)/.exec(name))) {
+ if (name && !product && /[/,]/.test(ua.slice(ua.indexOf(data + '/') + 8))) {
+ name = null;
+ }
+ if ((data = product || os) && !/^(?:iP|Lin|Mac|Win)/.test(data)) {
+ name = /[a-z]+/i.exec(/^And/.test(os) && os || data) + ' Browser';
+ }
+ }
+ // detect non Opera versions
+ if (!version) {
+ version = reduce(['version', name, 'AdobeAIR', 'Firefox', 'NetFront'], function(version, guess) {
+ return version || (RegExp(guess + '(?:-[\\d.]+/|[ /-])([^ ();/-]*)', 'i').exec(ua) || 0)[1] || null;
+ });
+ }
+ // detect server-side js
+ if (me && isHostType(me, 'global')) {
+ if (typeof exports == 'object' && exports) {
+ if (me == window && typeof system == 'object' && (data = system)) {
+ name = data.global == global ? 'Narwhal' : 'RingoJS';
+ os = data.os || null;
+ }
+ else if (typeof process == 'object' && (data = process)) {
+ name = 'Node.js';
+ version = /[\d.]+/.exec(data.version)[0];
+ os = data.platform;
+ }
+ } else if (isClassOf(me.environment, 'Environment')) {
+ name = 'Rhino';
+ }
+ if (IN_JAVA && !os) {
+ os = String(java.lang.System.getProperty('os.name'));
+ }
+ }
+ // detect Adobe AIR
+ else if (IN_AIR) {
+ name = 'Adobe AIR';
+ os = runtime.flash.system.Capabilities.os;
+ }
+ // detect PhantomJS
+ else if (isClassOf(data = window.phantom, 'RuntimeObject')) {
+ name = 'PhantomJS';
+ version = (data = data.version) && (data.major + '.' + data.minor + '.' + data.patch);
+ }
+ // detect IE compatibility mode
+ else if (typeof doc.documentMode == 'number' && (data = /Trident\/(\d+)/.exec(ua))) {
+ version = [version, doc.documentMode];
+ version[1] = (data = +data[1] + 4) != version[1] ? (layout = null, description.push('running in IE ' + version[1] + ' mode'), data) : version[1];
+ version = name == 'IE' ? String(version[1].toFixed(1)) : version[0];
+ }
+ // detect release phases
+ if (version && (data = /(?:[ab]|dp|pre|[ab]\d+pre)(?:\d+\+?)?$/i.exec(version) || /(?:alpha|beta)(?: ?\d)?/i.exec(ua + ';' + nav.appMinorVersion))) {
+ version = version.replace(RegExp(data + '\\+?$'), '') + (/^b/i.test(data) ? beta : alpha) + (/\d+\+?/.exec(data) || '');
+ }
+ // detect Maxthon's unreliable version info
+ if (/^Max/.test(name)) {
+ version = version && version.replace(/\.[.\d]*/, '.x');
+ }
+ // detect Firefox nightly
+ else if (/^Min/.test(name)) {
+ name = 'Firefox';
+ version = RegExp(alpha + '|' + beta + '|null').test(version) ? version : version + alpha;
+ }
+ // detect mobile
+ else if (name && (!product || name == 'IE') && !/Bro/.test(name) && /Mobi/.test(ua)) {
+ name += ' Mobile';
+ }
+ // detect unspecified Chrome/Safari versions
+ if (data = (/AppleWebKit\/(\d+(?:\.\d+)?)/.exec(ua) || 0)[1]) {
+ if (/^And/.exec(os)) {
+ data = data < 530 ? 1 : data < 532 ? 2 : data < 532.5 ? 3 : data < 533 ? 4 : data < 534.3 ? 5 : data < 534.7 ? 6 : data < 534.10 ? 7 : data < 534.13 ? 8 : data < 534.16 ? 9 : '10';
+ layout = 'like Chrome';
+ } else {
+ data = data < 400 ? 1 : data < 500 ? 2 : data < 526 ? 3 : data < 533 ? 4 : '4';
+ layout = 'like Safari';
+ }
+ layout += ' ' + (data += typeof data == 'number' ? '.x' : '+');
+ version = name == 'Safari' && (!version || parseInt(version) > 45) ? data : version;
+ }
+ // detect platform preview
+ if (RegExp(alpha + '|' + beta).test(version) && typeof external == 'object' && !external) {
+ layout = layout && !/like /.test(layout) ? 'rendered by ' + layout : layout;
+ description.unshift('platform preview');
+ }
+ // add layout engine
+ if (layout && /Ado|Bro|Lun|Max|Pha|Sle/.test(name)) {
+ description.push(layout);
+ }
+ // combine contextual information
+ if (description.length) {
+ description = ['(' + description.join('; ') + ')'];
+ }
+ // append product
+ if (product && String(name).indexOf(product) < 0) {
+ description.push('on ' + product);
+ }
+ // add browser/os architecture
+ if (/\b(?:WOW|x|IA)64\b/.test(ua)) {
+ os = os && os + (/64/.test(os) ? '' : ' x64');
+ if (name && (/WOW64/.test(ua) || /\w(?:86|32)$/.test(nav.cpuClass || nav.platform))) {
+ description.unshift('x86');
+ }
+ }
+ return {
+ 'version': name && version && description.unshift(version) && version,
+ 'name': name && description.unshift(name) && name,
+ 'os': name && (os = os && format(os)) && description.push(product ? '(' + os + ')' : 'on ' + os) && os,
+ 'product': product,
+ 'description': description.length ? description.join(' ') : ua,
+ 'toString': function() { return this.description; }
+ };
+ }());
+
+ /*--------------------------------------------------------------------------*/
+
+ extend(Benchmark, {
+
+ /**
+ * The version number.
+ * @static
+ * @member Benchmark
+ * @type String
+ */
+ 'version': '0.2.1',
+
+ /**
+ * The default options object copied by instances.
+ * @static
+ * @member Benchmark
+ * @type Object
+ */
+ 'options': { },
+
+ // generic Array#forEach/for-in
+ 'each': each,
+
+ // copy properties to another object
+ 'extend': extend,
+
+ // generic Array#filter
+ 'filter': filter,
+
+ // converts a number to a comma-separated string
+ 'formatNumber': formatNumber,
+
+ // xbrowser Object#hasOwnProperty
+ 'hasKey': hasKey,
+
+ // generic Array#indexOf
+ 'indexOf': indexOf,
+
+ // invokes a method on each item in an array
+ 'invoke': invoke,
+
+ // modifies a string using a template object
+ 'interpolate': interpolate,
+
+ // xbrowser Array.isArray
+ 'isArray': isArray,
+
+ // checks internal [[Class]] of an object
+ 'isClassOf': isClassOf,
+
+ // checks if an object's property is a non-primitive value
+ 'isHostType': isHostType,
+
+ // generic Array#join for arrays and objects
+ 'join': join,
+
+ // generic Array#map
+ 'map': map,
+
+ // no operation
+ 'noop': noop,
+
+ // retrieves a property value from each item in an array
+ 'pluck': pluck,
+
+ // generic Array#reduce
+ 'reduce': reduce,
+
+ // generic String#trim
+ 'trim': trim
+ });
+
+ /*--------------------------------------------------------------------------*/
+
+ // IE may ignore `toString` in a for-in loop
+ Benchmark.prototype.toString = toString;
+
+ extend(Benchmark.prototype, {
+
+ /**
+ * The delay between test cycles (secs).
+ * @member Benchmark
+ * @type Number
+ */
+ 'CYCLE_DELAY': 0.005,
+
+ /**
+ * A flag to indicate methods will run asynchronously by default.
+ * @member Benchmark
+ * @type Boolean
+ */
+ 'DEFAULT_ASYNC': false,
+
+ /**
+ * The default number of times to execute a test on a benchmark's first cycle.
+ * @member Benchmark
+ * @type Number
+ */
+ 'INIT_RUN_COUNT': 5,
+
+ /**
+ * The maximum time a benchmark is allowed to run before finishing (secs).
+ * @member Benchmark
+ * @type Number
+ */
+ 'MAX_TIME_ELAPSED': 5,
+
+ /**
+ * The minimum sample size required to perform statistical analysis.
+ * @member Benchmark
+ * @type Number
+ */
+ 'MIN_SAMPLE_SIZE': 5,
+
+ /**
+ * The time needed to reduce the percent uncertainty of measurement to 1% (secs).
+ * @member Benchmark
+ * @type Number
+ */
+ 'MIN_TIME': 0,
+
+ /**
+ * The number of times a test was executed.
+ * @member Benchmark
+ * @type Number
+ */
+ 'count': 0,
+
+ /**
+ * A timestamp of when the benchmark was created.
+ * @member Benchmark
+ * @type Number
+ */
+ 'created': 0,
+
+ /**
+ * The number of cycles performed while benchmarking.
+ * @member Benchmark
+ * @type Number
+ */
+ 'cycles': 0,
+
+ /**
+ * The error object if the test failed.
+ * @member Benchmark
+ * @type Object|Null
+ */
+ 'error': null,
+
+ /**
+ * The number of executions per second.
+ * @member Benchmark
+ * @type Number
+ */
+ 'hz': 0,
+
+ /**
+ * A flag to indicate if the benchmark is aborted.
+ * @member Benchmark
+ * @type Boolean
+ */
+ 'aborted': false,
+
+ /**
+ * A flag to indicate if the benchmark is running.
+ * @member Benchmark
+ * @type Boolean
+ */
+ 'running': false,
+
+ /**
+ * Alias of [`Benchmark#addListener`](#Benchmark:addListener).
+ * @member Benchmark, Benchmark.Suite
+ */
+ 'on': addListener,
+
+ /**
+ * Compiled into the test and executed immediately **before** the test loop.
+ * @member Benchmark
+ * @type Function
+ * @example
+ *
+ * var bench = new Benchmark({
+ * 'fn': function() {
+ * a += 1;
+ * },
+ * 'setup': function() {
+ * // reset local var `a` at the beginning of each test cycle
+ * a = 0;
+ * }
+ * });
+ *
+ * // compiles into something like:
+ * var a = 0;
+ * var start = new Date;
+ * while (count--) {
+ * a += 1;
+ * }
+ * var end = new Date - start;
+ */
+ 'setup': noop,
+
+ /**
+ * Compiled into the test and executed immediately **after** the test loop.
+ * @member Benchmark
+ * @type Function
+ */
+ 'teardown': noop,
+
+ /**
+ * An object of stats including mean, margin or error, and standard deviation.
+ * @member Benchmark
+ * @type Object
+ */
+ 'stats': {
+
+ /**
+ * The margin of error.
+ * @member Benchmark#stats
+ * @type Number
+ */
+ 'ME': 0,
+
+ /**
+ * The relative margin of error (expressed as a percentage of the mean).
+ * @member Benchmark#stats
+ * @type Number
+ */
+ 'RME': 0,
+
+ /**
+ * The standard error of the mean.
+ * @member Benchmark#stats
+ * @type Number
+ */
+ 'SEM': 0,
+
+ /**
+ * The sample standard deviation.
+ * @member Benchmark#stats
+ * @type Number
+ */
+ 'deviation': 0,
+
+ /**
+ * The sample arithmetic mean.
+ * @member Benchmark#stats
+ * @type Number
+ */
+ 'mean': 0,
+
+ /**
+ * The sample size.
+ * @member Benchmark#stats
+ * @type Number
+ */
+ 'size': 0,
+
+ /**
+ * The sample variance.
+ * @member Benchmark#stats
+ * @type Number
+ */
+ 'variance': 0
+ },
+
+ /**
+ * An object of timing data including cycle, elapsed, period, start, and stop.
+ * @member Benchmark
+ * @type Object
+ */
+ 'times': {
+
+ /**
+ * The time taken to complete the last cycle (secs)
+ * @member Benchmark#times
+ * @type Number
+ */
+ 'cycle': 0,
+
+ /**
+ * The time taken to complete the benchmark (secs).
+ * @member Benchmark#times
+ * @type Number
+ */
+ 'elapsed': 0,
+
+ /**
+ * The time taken to execute the test once (secs).
+ * @member Benchmark#times
+ * @type Number
+ */
+ 'period': 0,
+
+ /**
+ * A timestamp of when the benchmark started (ms).
+ * @member Benchmark#times
+ * @type Number
+ */
+ 'start': 0,
+
+ /**
+ * A timestamp of when the benchmark finished (ms).
+ * @member Benchmark#times
+ * @type Number
+ */
+ 'stop': 0
+ },
+
+ // aborts benchmark (does not record times)
+ 'abort': abort,
+
+ // registers a single listener
+ 'addListener': addListener,
+
+ // creates a new benchmark using the same test and options
+ 'clone': clone,
+
+ // compares benchmark's hertz with another
+ 'compare': compare,
+
+ // executes listeners of a specified type
+ 'emit': emit,
+
+ // removes all listeners of a specified type
+ 'removeAllListeners': removeAllListeners,
+
+ // removes a single listener
+ 'removeListener': removeListener,
+
+ // reset benchmark properties
+ 'reset': reset,
+
+ // runs the benchmark
+ 'run': run
+ });
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * The default options object copied by instances.
+ * @static
+ * @member Benchmark.Suite
+ * @type Object
+ */
+ Suite.options = { };
+
+ /*--------------------------------------------------------------------------*/
+
+ extend(Suite.prototype, {
+
+ /**
+ * The number of benchmarks in the suite.
+ * @member Benchmark.Suite
+ * @type Number
+ */
+ 'length': 0,
+
+ /**
+ * A flag to indicate if the suite is aborted.
+ * @member Benchmark.Suite
+ * @type Boolean
+ */
+ 'aborted': false,
+
+ /**
+ * A flag to indicate if the suite is running.
+ * @member Benchmark.Suite
+ * @type Boolean
+ */
+ 'running': false,
+
+ /**
+ * A bare-bones `Array#forEach` solution.
+ * Callbacks may terminate the loop by explicitly returning `false`.
+ * @member Benchmark.Suite
+ * @param {Function} callback The function called per iteration.
+ * @returns {Object} The suite iterated over.
+ */
+ 'each': methodize(each),
+
+ /**
+ * A bare-bones `Array#indexOf` solution.
+ * @member Benchmark.Suite
+ * @param {Mixed} value The value to search for.
+ * @returns {Number} The index of the matched value or `-1`.
+ */
+ 'indexOf': methodize(indexOf),
+
+ /**
+ * Invokes a method on all benchmarks in the suite.
+ * @member Benchmark.Suite
+ * @param {String|Object} name The name of the method to invoke OR options object.
+ * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the method with.
+ * @returns {Array} A new array of values returned from each method invoked.
+ */
+ 'invoke': methodize(invoke),
+
+ /**
+ * A bare-bones `Array#map` solution.
+ * @member Benchmark.Suite
+ * @param {Function} callback The function called per iteration.
+ * @returns {Array} A new array of values returned by the callback.
+ */
+ 'map': methodize(map),
+
+ /**
+ * Retrieves the value of a specified property from all benchmarks in the suite.
+ * @member Benchmark.Suite
+ * @param {String} property The property to pluck.
+ * @returns {Array} A new array of property values.
+ */
+ 'pluck': methodize(pluck),
+
+ /**
+ * A bare-bones `Array#reduce` solution.
+ * @member Benchmark.Suite
+ * @param {Function} callback The function called per iteration.
+ * @param {Mixed} accumulator Initial value of the accumulator.
+ * @returns {Mixed} The accumulator.
+ */
+ 'reduce': methodize(reduce),
+
+ // aborts all benchmarks in the suite
+ 'abort': abortSuite,
+
+ // adds a benchmark to the suite
+ 'add': add,
+
+ // registers a single listener
+ 'addListener': addListener,
+
+ // creates a new suite with cloned benchmarks
+ 'clone': cloneSuite,
+
+ // executes listeners of a specified type
+ 'emit': emit,
+
+ // creates a new suite of filtered benchmarks
+ 'filter': filterSuite,
+
+ // alias of addListener
+ 'on': addListener,
+
+ // removes all listeners of a specified type
+ 'removeAllListeners': removeAllListeners,
+
+ // removes a single listener
+ 'removeListener': removeListener,
+
+ // resets all benchmarks in the suite
+ 'reset': resetSuite,
+
+ // runs all benchmarks in the suite
+ 'run': runSuite,
+
+ // array methods
+ 'concat': [].concat,
+
+ 'join': [].join,
+
+ 'pop': aloClean([].pop),
+
+ 'push': [].push,
+
+ 'reverse': [].reverse,
+
+ 'shift': shift,
+
+ 'slice': slice,
+
+ 'sort': [].sort,
+
+ 'splice': aloClean([].splice),
+
+ 'unshift': [].unshift
+ });
+
+ /*--------------------------------------------------------------------------*/
+
+ // expose Suite
+ Benchmark.Suite = Suite;
+
+ // expose Benchmark
+ if (typeof exports == 'object' && exports && typeof global == 'object' && global) {
+ window = global;
+ if (typeof module == 'object' && module && module.exports == exports) {
+ module.exports = Benchmark;
+ } else {
+ exports.Benchmark = Benchmark;
+ }
+ } else {
+ window.Benchmark = Benchmark;
+ }
+
+ // trigger clock's lazy define early to avoid a security error
+ if (IN_AIR) {
+ clock({ 'fn': noop, 'count': 1 });
+ }
+
+ // feature detect
+ HAS_TIMEOUT_API = isHostType(window, 'setTimeout') &&
+ isHostType(window, 'clearTimeout');
+
+}(this));
\ No newline at end of file
diff --git a/test/benchmark/nano.jar b/test/benchmark/nano.jar
new file mode 100644
index 0000000..b577840
Binary files /dev/null and b/test/benchmark/nano.jar differ