mirror of
https://github.com/whoisclebs/lodash.git
synced 2026-02-03 00:27:50 +00:00
Add json3.js, remove json2 and jslitmus from vendors.
Former-commit-id: d801c48ec81ea2f5c5638910190da461facf98cf
This commit is contained in:
@@ -8,9 +8,6 @@
|
||||
body > #qunit-header {
|
||||
display: none;
|
||||
}
|
||||
#jslitmus {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -21,9 +18,8 @@
|
||||
<h1>Test</h1>
|
||||
</div>
|
||||
</div>
|
||||
<script src="../vendor/backbone/test/vendor/json2.js"></script>
|
||||
<script src="../vendor/json3/lib/json3.js"></script>
|
||||
<script src="../vendor/backbone/test/vendor/jquery-1.7.1.js"></script>
|
||||
<script src="../vendor/backbone/test/vendor/jslitmus.js"></script>
|
||||
<script src="../vendor/platform.js/platform.js"></script>
|
||||
<script>
|
||||
// avoid syntax errors for `QUnit.throws` in older Firefoxes
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<title>Underscore Test Suite</title>
|
||||
<link rel="stylesheet" href="../vendor/underscore/test/vendor/qunit.css">
|
||||
<style>
|
||||
#jslitmus, iframe {
|
||||
iframe, img {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
@@ -17,10 +17,10 @@
|
||||
<div id="id1"></div>
|
||||
<div id="id2"></div>
|
||||
</div>
|
||||
<img id="chart_image" src="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==">
|
||||
</div>
|
||||
<script src="../vendor/backbone/test/vendor/json2.js"></script>
|
||||
<script src="../vendor/json3/lib/json3.js"></script>
|
||||
<script src="../vendor/underscore/test/vendor/jquery.js"></script>
|
||||
<script src="../vendor/underscore/test/vendor/jslitmus.js"></script>
|
||||
<script src="../vendor/platform.js/platform.js"></script>
|
||||
<script>
|
||||
// avoid syntax errors for `QUnit.throws` in older Firefoxes
|
||||
|
||||
649
vendor/backbone/test/vendor/jslitmus.js
vendored
649
vendor/backbone/test/vendor/jslitmus.js
vendored
@@ -1,649 +0,0 @@
|
||||
// JSLitmus.js
|
||||
//
|
||||
// Copyright (c) 2010, Robert Kieffer, http://broofa.com
|
||||
// Available under MIT license (http://en.wikipedia.org/wiki/MIT_License)
|
||||
|
||||
(function() {
|
||||
// Private methods and state
|
||||
|
||||
// Get platform info but don't go crazy trying to recognize everything
|
||||
// that's out there. This is just for the major platforms and OSes.
|
||||
var platform = 'unknown platform', ua = navigator.userAgent;
|
||||
|
||||
// Detect OS
|
||||
var oses = ['Windows','iPhone OS','(Intel |PPC )?Mac OS X','Linux'].join('|');
|
||||
var pOS = new RegExp('((' + oses + ') [^ \);]*)').test(ua) ? RegExp.$1 : null;
|
||||
if (!pOS) pOS = new RegExp('((' + oses + ')[^ \);]*)').test(ua) ? RegExp.$1 : null;
|
||||
|
||||
// Detect browser
|
||||
var pName = /(Chrome|MSIE|Safari|Opera|Firefox)/.test(ua) ? RegExp.$1 : null;
|
||||
|
||||
// Detect version
|
||||
var vre = new RegExp('(Version|' + pName + ')[ \/]([^ ;]*)');
|
||||
var pVersion = (pName && vre.test(ua)) ? RegExp.$2 : null;
|
||||
var platform = (pOS && pName && pVersion) ? pName + ' ' + pVersion + ' on ' + pOS : 'unknown platform';
|
||||
|
||||
/**
|
||||
* A smattering of methods that are needed to implement the JSLitmus testbed.
|
||||
*/
|
||||
var jsl = {
|
||||
/**
|
||||
* Enhanced version of escape()
|
||||
*/
|
||||
escape: function(s) {
|
||||
s = s.replace(/,/g, '\\,');
|
||||
s = escape(s);
|
||||
s = s.replace(/\+/g, '%2b');
|
||||
s = s.replace(/ /g, '+');
|
||||
return s;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get an element by ID.
|
||||
*/
|
||||
$: function(id) {
|
||||
return document.getElementById(id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Null function
|
||||
*/
|
||||
F: function() {},
|
||||
|
||||
/**
|
||||
* Set the status shown in the UI
|
||||
*/
|
||||
status: function(msg) {
|
||||
var el = jsl.$('jsl_status');
|
||||
if (el) el.innerHTML = msg || '';
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert a number to an abbreviated string like, "15K" or "10M"
|
||||
*/
|
||||
toLabel: function(n) {
|
||||
if (n == Infinity) {
|
||||
return 'Infinity';
|
||||
} else if (n > 1e9) {
|
||||
n = Math.round(n/1e8);
|
||||
return n/10 + 'B';
|
||||
} else if (n > 1e6) {
|
||||
n = Math.round(n/1e5);
|
||||
return n/10 + 'M';
|
||||
} else if (n > 1e3) {
|
||||
n = Math.round(n/1e2);
|
||||
return n/10 + 'K';
|
||||
}
|
||||
return n;
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy properties from src to dst
|
||||
*/
|
||||
extend: function(dst, src) {
|
||||
for (var k in src) dst[k] = src[k]; return dst;
|
||||
},
|
||||
|
||||
/**
|
||||
* Like Array.join(), but for the key-value pairs in an object
|
||||
*/
|
||||
join: function(o, delimit1, delimit2) {
|
||||
if (o.join) return o.join(delimit1); // If it's an array
|
||||
var pairs = [];
|
||||
for (var k in o) pairs.push(k + delimit1 + o[k]);
|
||||
return pairs.join(delimit2);
|
||||
},
|
||||
|
||||
/**
|
||||
* Array#indexOf isn't supported in IE, so we use this as a cross-browser solution
|
||||
*/
|
||||
indexOf: function(arr, o) {
|
||||
if (arr.indexOf) return arr.indexOf(o);
|
||||
for (var i = 0; i < this.length; i++) if (arr[i] === o) return i;
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Test manages a single test (created with
|
||||
* JSLitmus.test())
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
var Test = function (name, f) {
|
||||
if (!f) throw new Error('Undefined test function');
|
||||
if (!/function[^\(]*\(([^,\)]*)/.test(f.toString())) {
|
||||
throw new Error('"' + name + '" test: Test is not a valid Function object');
|
||||
}
|
||||
this.loopArg = RegExp.$1;
|
||||
this.name = name;
|
||||
this.f = f;
|
||||
};
|
||||
|
||||
jsl.extend(Test, /** @lends Test */ {
|
||||
/** Calibration tests for establishing iteration loop overhead */
|
||||
CALIBRATIONS: [
|
||||
new Test('calibrating loop', function(count) {while (count--);}),
|
||||
new Test('calibrating function', jsl.F)
|
||||
],
|
||||
|
||||
/**
|
||||
* Run calibration tests. Returns true if calibrations are not yet
|
||||
* complete (in which case calling code should run the tests yet again).
|
||||
* onCalibrated - Callback to invoke when calibrations have finished
|
||||
*/
|
||||
calibrate: function(onCalibrated) {
|
||||
for (var i = 0; i < Test.CALIBRATIONS.length; i++) {
|
||||
var cal = Test.CALIBRATIONS[i];
|
||||
if (cal.running) return true;
|
||||
if (!cal.count) {
|
||||
cal.isCalibration = true;
|
||||
cal.onStop = onCalibrated;
|
||||
//cal.MIN_TIME = .1; // Do calibrations quickly
|
||||
cal.run(2e4);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
jsl.extend(Test.prototype, {/** @lends Test.prototype */
|
||||
/** Initial number of iterations */
|
||||
INIT_COUNT: 10,
|
||||
/** Max iterations allowed (i.e. used to detect bad looping functions) */
|
||||
MAX_COUNT: 1e9,
|
||||
/** Minimum time a test should take to get valid results (secs) */
|
||||
MIN_TIME: .5,
|
||||
|
||||
/** Callback invoked when test state changes */
|
||||
onChange: jsl.F,
|
||||
|
||||
/** Callback invoked when test is finished */
|
||||
onStop: jsl.F,
|
||||
|
||||
/**
|
||||
* Reset test state
|
||||
*/
|
||||
reset: function() {
|
||||
delete this.count;
|
||||
delete this.time;
|
||||
delete this.running;
|
||||
delete this.error;
|
||||
},
|
||||
|
||||
/**
|
||||
* Run the test (in a timeout). We use a timeout to make sure the browser
|
||||
* has a chance to finish rendering any UI changes we've made, like
|
||||
* updating the status message.
|
||||
*/
|
||||
run: function(count) {
|
||||
count = count || this.INIT_COUNT;
|
||||
jsl.status(this.name + ' x ' + count);
|
||||
this.running = true;
|
||||
var me = this;
|
||||
setTimeout(function() {me._run(count);}, 200);
|
||||
},
|
||||
|
||||
/**
|
||||
* The nuts and bolts code that actually runs a test
|
||||
*/
|
||||
_run: function(count) {
|
||||
var me = this;
|
||||
|
||||
// Make sure calibration tests have run
|
||||
if (!me.isCalibration && Test.calibrate(function() {me.run(count);})) return;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
var start, f = this.f, now, i = count;
|
||||
|
||||
// Start the timer
|
||||
start = new Date();
|
||||
|
||||
// Now for the money shot. If this is a looping function ...
|
||||
if (this.loopArg) {
|
||||
// ... let it do the iteration itself
|
||||
f(count);
|
||||
} else {
|
||||
// ... otherwise do the iteration for it
|
||||
while (i--) f();
|
||||
}
|
||||
|
||||
// Get time test took (in secs)
|
||||
this.time = Math.max(1,new Date() - start)/1000;
|
||||
|
||||
// Store iteration count and per-operation time taken
|
||||
this.count = count;
|
||||
this.period = this.time/count;
|
||||
|
||||
// Do we need to do another run?
|
||||
this.running = this.time <= this.MIN_TIME;
|
||||
|
||||
// ... if so, compute how many times we should iterate
|
||||
if (this.running) {
|
||||
// Bump the count to the nearest power of 2
|
||||
var x = this.MIN_TIME/this.time;
|
||||
var pow = Math.pow(2, Math.max(1, Math.ceil(Math.log(x)/Math.log(2))));
|
||||
count *= pow;
|
||||
if (count > this.MAX_COUNT) {
|
||||
throw new Error('Max count exceeded. If this test uses a looping function, make sure the iteration loop is working properly.');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Exceptions are caught and displayed in the test UI
|
||||
this.reset();
|
||||
this.error = e;
|
||||
}
|
||||
|
||||
// Figure out what to do next
|
||||
if (this.running) {
|
||||
me.run(count);
|
||||
} else {
|
||||
jsl.status('');
|
||||
me.onStop(me);
|
||||
}
|
||||
|
||||
// Finish up
|
||||
this.onChange(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the number of operations per second for this test.
|
||||
*
|
||||
* @param normalize if true, iteration loop overhead taken into account
|
||||
*/
|
||||
getHz: function(/**Boolean*/ normalize) {
|
||||
var p = this.period;
|
||||
|
||||
// Adjust period based on the calibration test time
|
||||
if (normalize && !this.isCalibration) {
|
||||
var cal = Test.CALIBRATIONS[this.loopArg ? 0 : 1];
|
||||
|
||||
// If the period is within 20% of the calibration time, then zero the
|
||||
// it out
|
||||
p = p < cal.period*1.2 ? 0 : p - cal.period;
|
||||
}
|
||||
|
||||
return Math.round(1/p);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a friendly string describing the test
|
||||
*/
|
||||
toString: function() {
|
||||
return this.name + ' - ' + this.time/this.count + ' secs';
|
||||
}
|
||||
});
|
||||
|
||||
// CSS we need for the UI
|
||||
var STYLESHEET = '<style> \
|
||||
#jslitmus {font-family:sans-serif; font-size: 12px;} \
|
||||
#jslitmus a {text-decoration: none;} \
|
||||
#jslitmus a:hover {text-decoration: underline;} \
|
||||
#jsl_status { \
|
||||
margin-top: 10px; \
|
||||
font-size: 10px; \
|
||||
color: #888; \
|
||||
} \
|
||||
A IMG {border:none} \
|
||||
#test_results { \
|
||||
margin-top: 10px; \
|
||||
font-size: 12px; \
|
||||
font-family: sans-serif; \
|
||||
border-collapse: collapse; \
|
||||
border-spacing: 0px; \
|
||||
} \
|
||||
#test_results th, #test_results td { \
|
||||
border: solid 1px #ccc; \
|
||||
vertical-align: top; \
|
||||
padding: 3px; \
|
||||
} \
|
||||
#test_results th { \
|
||||
vertical-align: bottom; \
|
||||
background-color: #ccc; \
|
||||
padding: 1px; \
|
||||
font-size: 10px; \
|
||||
} \
|
||||
#test_results #test_platform { \
|
||||
color: #444; \
|
||||
text-align:center; \
|
||||
} \
|
||||
#test_results .test_row { \
|
||||
color: #006; \
|
||||
cursor: pointer; \
|
||||
} \
|
||||
#test_results .test_nonlooping { \
|
||||
border-left-style: dotted; \
|
||||
border-left-width: 2px; \
|
||||
} \
|
||||
#test_results .test_looping { \
|
||||
border-left-style: solid; \
|
||||
border-left-width: 2px; \
|
||||
} \
|
||||
#test_results .test_name {white-space: nowrap;} \
|
||||
#test_results .test_pending { \
|
||||
} \
|
||||
#test_results .test_running { \
|
||||
font-style: italic; \
|
||||
} \
|
||||
#test_results .test_done {} \
|
||||
#test_results .test_done { \
|
||||
text-align: right; \
|
||||
font-family: monospace; \
|
||||
} \
|
||||
#test_results .test_error {color: #600;} \
|
||||
#test_results .test_error .error_head {font-weight:bold;} \
|
||||
#test_results .test_error .error_body {font-size:85%;} \
|
||||
#test_results .test_row:hover td { \
|
||||
background-color: #ffc; \
|
||||
text-decoration: underline; \
|
||||
} \
|
||||
#chart { \
|
||||
margin: 10px 0px; \
|
||||
width: 250px; \
|
||||
} \
|
||||
#chart img { \
|
||||
border: solid 1px #ccc; \
|
||||
margin-bottom: 5px; \
|
||||
} \
|
||||
#chart #tiny_url { \
|
||||
height: 40px; \
|
||||
width: 250px; \
|
||||
} \
|
||||
#jslitmus_credit { \
|
||||
font-size: 10px; \
|
||||
color: #888; \
|
||||
margin-top: 8px; \
|
||||
} \
|
||||
</style>';
|
||||
|
||||
// HTML markup for the UI
|
||||
var MARKUP = '<div id="jslitmus"> \
|
||||
<button onclick="JSLitmus.runAll(event)">Run Tests</button> \
|
||||
<button id="stop_button" disabled="disabled" onclick="JSLitmus.stop()">Stop Tests</button> \
|
||||
<br \> \
|
||||
<br \> \
|
||||
<input type="checkbox" style="vertical-align: middle" id="test_normalize" checked="checked" onchange="JSLitmus.renderAll()""> Normalize results \
|
||||
<table id="test_results"> \
|
||||
<colgroup> \
|
||||
<col /> \
|
||||
<col width="100" /> \
|
||||
</colgroup> \
|
||||
<tr><th id="test_platform" colspan="2">' + platform + '</th></tr> \
|
||||
<tr><th>Test</th><th>Ops/sec</th></tr> \
|
||||
<tr id="test_row_template" class="test_row" style="display:none"> \
|
||||
<td class="test_name"></td> \
|
||||
<td class="test_result">Ready</td> \
|
||||
</tr> \
|
||||
</table> \
|
||||
<div id="jsl_status"></div> \
|
||||
<div id="chart" style="display:none"> \
|
||||
<a id="chart_link" target="_blank"><img id="chart_image"></a> \
|
||||
TinyURL (for chart): \
|
||||
<iframe id="tiny_url" frameBorder="0" scrolling="no" src=""></iframe> \
|
||||
</div> \
|
||||
<a id="jslitmus_credit" title="JSLitmus home page" href="http://code.google.com/p/jslitmus" target="_blank">Powered by JSLitmus</a> \
|
||||
</div>';
|
||||
|
||||
/**
|
||||
* The public API for creating and running tests
|
||||
*/
|
||||
window.JSLitmus = {
|
||||
/** The list of all tests that have been registered with JSLitmus.test */
|
||||
_tests: [],
|
||||
/** The queue of tests that need to be run */
|
||||
_queue: [],
|
||||
|
||||
/**
|
||||
* The parsed query parameters the current page URL. This is provided as a
|
||||
* convenience for test functions - it's not used by JSLitmus proper
|
||||
*/
|
||||
params: {},
|
||||
|
||||
/**
|
||||
* Initialize
|
||||
*/
|
||||
_init: function() {
|
||||
// Parse query params into JSLitmus.params[] hash
|
||||
var match = (location + '').match(/([^?#]*)(#.*)?$/);
|
||||
if (match) {
|
||||
var pairs = match[1].split('&');
|
||||
for (var i = 0; i < pairs.length; i++) {
|
||||
var pair = pairs[i].split('=');
|
||||
if (pair.length > 1) {
|
||||
var key = pair.shift();
|
||||
var value = pair.length > 1 ? pair.join('=') : pair[0];
|
||||
this.params[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write out the stylesheet. We have to do this here because IE
|
||||
// doesn't honor sheets written after the document has loaded.
|
||||
document.write(STYLESHEET);
|
||||
|
||||
// Setup the rest of the UI once the document is loaded
|
||||
if (window.addEventListener) {
|
||||
window.addEventListener('load', this._setup, false);
|
||||
} else if (document.addEventListener) {
|
||||
document.addEventListener('load', this._setup, false);
|
||||
} else if (window.attachEvent) {
|
||||
window.attachEvent('onload', this._setup);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set up the UI
|
||||
*/
|
||||
_setup: function() {
|
||||
var el = jsl.$('jslitmus_container');
|
||||
if (!el) document.body.appendChild(el = document.createElement('div'));
|
||||
|
||||
el.innerHTML = MARKUP;
|
||||
|
||||
// Render the UI for all our tests
|
||||
for (var i=0; i < JSLitmus._tests.length; i++)
|
||||
JSLitmus.renderTest(JSLitmus._tests[i]);
|
||||
},
|
||||
|
||||
/**
|
||||
* (Re)render all the test results
|
||||
*/
|
||||
renderAll: function() {
|
||||
for (var i = 0; i < JSLitmus._tests.length; i++)
|
||||
JSLitmus.renderTest(JSLitmus._tests[i]);
|
||||
JSLitmus.renderChart();
|
||||
},
|
||||
|
||||
/**
|
||||
* (Re)render the chart graphics
|
||||
*/
|
||||
renderChart: function() {
|
||||
var url = JSLitmus.chartUrl();
|
||||
jsl.$('chart_link').href = url;
|
||||
jsl.$('chart_image').src = url;
|
||||
jsl.$('chart').style.display = '';
|
||||
|
||||
// Update the tiny URL
|
||||
jsl.$('tiny_url').src = 'http://tinyurl.com/api-create.php?url='+escape(url);
|
||||
},
|
||||
|
||||
/**
|
||||
* (Re)render the results for a specific test
|
||||
*/
|
||||
renderTest: function(test) {
|
||||
// Make a new row if needed
|
||||
if (!test._row) {
|
||||
var trow = jsl.$('test_row_template');
|
||||
if (!trow) return;
|
||||
|
||||
test._row = trow.cloneNode(true);
|
||||
test._row.style.display = '';
|
||||
test._row.id = '';
|
||||
test._row.onclick = function() {JSLitmus._queueTest(test);};
|
||||
test._row.title = 'Run ' + test.name + ' test';
|
||||
trow.parentNode.appendChild(test._row);
|
||||
test._row.cells[0].innerHTML = test.name;
|
||||
}
|
||||
|
||||
var cell = test._row.cells[1];
|
||||
var cns = [test.loopArg ? 'test_looping' : 'test_nonlooping'];
|
||||
|
||||
if (test.error) {
|
||||
cns.push('test_error');
|
||||
cell.innerHTML =
|
||||
'<div class="error_head">' + test.error + '</div>' +
|
||||
'<ul class="error_body"><li>' +
|
||||
jsl.join(test.error, ': ', '</li><li>') +
|
||||
'</li></ul>';
|
||||
} else {
|
||||
if (test.running) {
|
||||
cns.push('test_running');
|
||||
cell.innerHTML = 'running';
|
||||
} else if (jsl.indexOf(JSLitmus._queue, test) >= 0) {
|
||||
cns.push('test_pending');
|
||||
cell.innerHTML = 'pending';
|
||||
} else if (test.count) {
|
||||
cns.push('test_done');
|
||||
var hz = test.getHz(jsl.$('test_normalize').checked);
|
||||
cell.innerHTML = hz != Infinity ? hz : '∞';
|
||||
cell.title = 'Looped ' + test.count + ' times in ' + test.time + ' seconds';
|
||||
} else {
|
||||
cell.innerHTML = 'ready';
|
||||
}
|
||||
}
|
||||
cell.className = cns.join(' ');
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new test
|
||||
*/
|
||||
test: function(name, f) {
|
||||
// Create the Test object
|
||||
var test = new Test(name, f);
|
||||
JSLitmus._tests.push(test);
|
||||
|
||||
// Re-render if the test state changes
|
||||
test.onChange = JSLitmus.renderTest;
|
||||
|
||||
// Run the next test if this one finished
|
||||
test.onStop = function(test) {
|
||||
if (JSLitmus.onTestFinish) JSLitmus.onTestFinish(test);
|
||||
JSLitmus.currentTest = null;
|
||||
JSLitmus._nextTest();
|
||||
};
|
||||
|
||||
// Render the new test
|
||||
this.renderTest(test);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add all tests to the run queue
|
||||
*/
|
||||
runAll: function(e) {
|
||||
e = e || window.event;
|
||||
var reverse = e && e.shiftKey, len = JSLitmus._tests.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
JSLitmus._queueTest(JSLitmus._tests[!reverse ? i : (len - i - 1)]);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove all tests from the run queue. The current test has to finish on
|
||||
* it's own though
|
||||
*/
|
||||
stop: function() {
|
||||
while (JSLitmus._queue.length) {
|
||||
var test = JSLitmus._queue.shift();
|
||||
JSLitmus.renderTest(test);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Run the next test in the run queue
|
||||
*/
|
||||
_nextTest: function() {
|
||||
if (!JSLitmus.currentTest) {
|
||||
var test = JSLitmus._queue.shift();
|
||||
if (test) {
|
||||
jsl.$('stop_button').disabled = false;
|
||||
JSLitmus.currentTest = test;
|
||||
test.run();
|
||||
JSLitmus.renderTest(test);
|
||||
if (JSLitmus.onTestStart) JSLitmus.onTestStart(test);
|
||||
} else {
|
||||
jsl.$('stop_button').disabled = true;
|
||||
JSLitmus.renderChart();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a test to the run queue
|
||||
*/
|
||||
_queueTest: function(test) {
|
||||
if (jsl.indexOf(JSLitmus._queue, test) >= 0) return;
|
||||
JSLitmus._queue.push(test);
|
||||
JSLitmus.renderTest(test);
|
||||
JSLitmus._nextTest();
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate a Google Chart URL that shows the data for all tests
|
||||
*/
|
||||
chartUrl: function() {
|
||||
var n = JSLitmus._tests.length, markers = [], data = [];
|
||||
var d, min = 0, max = -1e10;
|
||||
var normalize = jsl.$('test_normalize').checked;
|
||||
|
||||
// Gather test data
|
||||
for (var i=0; i < JSLitmus._tests.length; i++) {
|
||||
var test = JSLitmus._tests[i];
|
||||
if (test.count) {
|
||||
var hz = test.getHz(normalize);
|
||||
var v = hz != Infinity ? hz : 0;
|
||||
data.push(v);
|
||||
markers.push('t' + jsl.escape(test.name + '(' + jsl.toLabel(hz)+ ')') + ',000000,0,' +
|
||||
markers.length + ',10');
|
||||
max = Math.max(v, max);
|
||||
}
|
||||
}
|
||||
if (markers.length <= 0) return null;
|
||||
|
||||
// Build chart title
|
||||
var title = document.getElementsByTagName('title');
|
||||
title = (title && title.length) ? title[0].innerHTML : null;
|
||||
var chart_title = [];
|
||||
if (title) chart_title.push(title);
|
||||
chart_title.push('Ops/sec (' + platform + ')');
|
||||
|
||||
// Build labels
|
||||
var labels = [jsl.toLabel(min), jsl.toLabel(max)];
|
||||
|
||||
var w = 250, bw = 15;
|
||||
var bs = 5;
|
||||
var h = markers.length*(bw + bs) + 30 + chart_title.length*20;
|
||||
|
||||
var params = {
|
||||
chtt: escape(chart_title.join('|')),
|
||||
chts: '000000,10',
|
||||
cht: 'bhg', // chart type
|
||||
chd: 't:' + data.join(','), // data set
|
||||
chds: min + ',' + max, // max/min of data
|
||||
chxt: 'x', // label axes
|
||||
chxl: '0:|' + labels.join('|'), // labels
|
||||
chsp: '0,1',
|
||||
chm: markers.join('|'), // test names
|
||||
chbh: [bw, 0, bs].join(','), // bar widths
|
||||
// chf: 'bg,lg,0,eeeeee,0,eeeeee,.5,ffffff,1', // gradient
|
||||
chs: w + 'x' + h
|
||||
};
|
||||
return 'http://chart.apis.google.com/chart?' + jsl.join(params, '=', '&');
|
||||
}
|
||||
};
|
||||
|
||||
JSLitmus._init();
|
||||
})();
|
||||
481
vendor/backbone/test/vendor/json2.js
vendored
481
vendor/backbone/test/vendor/json2.js
vendored
@@ -1,481 +0,0 @@
|
||||
/*
|
||||
http://www.JSON.org/json2.js
|
||||
2009-09-29
|
||||
|
||||
Public Domain.
|
||||
|
||||
NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
|
||||
|
||||
See http://www.JSON.org/js.html
|
||||
|
||||
|
||||
This code should be minified before deployment.
|
||||
See http://javascript.crockford.com/jsmin.html
|
||||
|
||||
USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
|
||||
NOT CONTROL.
|
||||
|
||||
|
||||
This file creates a global JSON object containing two methods: stringify
|
||||
and parse.
|
||||
|
||||
JSON.stringify(value, replacer, space)
|
||||
value any JavaScript value, usually an object or array.
|
||||
|
||||
replacer an optional parameter that determines how object
|
||||
values are stringified for objects. It can be a
|
||||
function or an array of strings.
|
||||
|
||||
space an optional parameter that specifies the indentation
|
||||
of nested structures. If it is omitted, the text will
|
||||
be packed without extra whitespace. If it is a number,
|
||||
it will specify the number of spaces to indent at each
|
||||
level. If it is a string (such as '\t' or ' '),
|
||||
it contains the characters used to indent at each level.
|
||||
|
||||
This method produces a JSON text from a JavaScript value.
|
||||
|
||||
When an object value is found, if the object contains a toJSON
|
||||
method, its toJSON method will be called and the result will be
|
||||
stringified. A toJSON method does not serialize: it returns the
|
||||
value represented by the name/value pair that should be serialized,
|
||||
or undefined if nothing should be serialized. The toJSON method
|
||||
will be passed the key associated with the value, and this will be
|
||||
bound to the value
|
||||
|
||||
For example, this would serialize Dates as ISO strings.
|
||||
|
||||
Date.prototype.toJSON = function (key) {
|
||||
function f(n) {
|
||||
// Format integers to have at least two digits.
|
||||
return n < 10 ? '0' + n : n;
|
||||
}
|
||||
|
||||
return this.getUTCFullYear() + '-' +
|
||||
f(this.getUTCMonth() + 1) + '-' +
|
||||
f(this.getUTCDate()) + 'T' +
|
||||
f(this.getUTCHours()) + ':' +
|
||||
f(this.getUTCMinutes()) + ':' +
|
||||
f(this.getUTCSeconds()) + 'Z';
|
||||
};
|
||||
|
||||
You can provide an optional replacer method. It will be passed the
|
||||
key and value of each member, with this bound to the containing
|
||||
object. The value that is returned from your method will be
|
||||
serialized. If your method returns undefined, then the member will
|
||||
be excluded from the serialization.
|
||||
|
||||
If the replacer parameter is an array of strings, then it will be
|
||||
used to select the members to be serialized. It filters the results
|
||||
such that only members with keys listed in the replacer array are
|
||||
stringified.
|
||||
|
||||
Values that do not have JSON representations, such as undefined or
|
||||
functions, will not be serialized. Such values in objects will be
|
||||
dropped; in arrays they will be replaced with null. You can use
|
||||
a replacer function to replace those with JSON values.
|
||||
JSON.stringify(undefined) returns undefined.
|
||||
|
||||
The optional space parameter produces a stringification of the
|
||||
value that is filled with line breaks and indentation to make it
|
||||
easier to read.
|
||||
|
||||
If the space parameter is a non-empty string, then that string will
|
||||
be used for indentation. If the space parameter is a number, then
|
||||
the indentation will be that many spaces.
|
||||
|
||||
Example:
|
||||
|
||||
text = JSON.stringify(['e', {pluribus: 'unum'}]);
|
||||
// text is '["e",{"pluribus":"unum"}]'
|
||||
|
||||
|
||||
text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
|
||||
// text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
|
||||
|
||||
text = JSON.stringify([new Date()], function (key, value) {
|
||||
return this[key] instanceof Date ?
|
||||
'Date(' + this[key] + ')' : value;
|
||||
});
|
||||
// text is '["Date(---current time---)"]'
|
||||
|
||||
|
||||
JSON.parse(text, reviver)
|
||||
This method parses a JSON text to produce an object or array.
|
||||
It can throw a SyntaxError exception.
|
||||
|
||||
The optional reviver parameter is a function that can filter and
|
||||
transform the results. It receives each of the keys and values,
|
||||
and its return value is used instead of the original value.
|
||||
If it returns what it received, then the structure is not modified.
|
||||
If it returns undefined then the member is deleted.
|
||||
|
||||
Example:
|
||||
|
||||
// Parse the text. Values that look like ISO date strings will
|
||||
// be converted to Date objects.
|
||||
|
||||
myData = JSON.parse(text, function (key, value) {
|
||||
var a;
|
||||
if (typeof value === 'string') {
|
||||
a =
|
||||
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
|
||||
if (a) {
|
||||
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
|
||||
+a[5], +a[6]));
|
||||
}
|
||||
}
|
||||
return value;
|
||||
});
|
||||
|
||||
myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
|
||||
var d;
|
||||
if (typeof value === 'string' &&
|
||||
value.slice(0, 5) === 'Date(' &&
|
||||
value.slice(-1) === ')') {
|
||||
d = new Date(value.slice(5, -1));
|
||||
if (d) {
|
||||
return d;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
});
|
||||
|
||||
|
||||
This is a reference implementation. You are free to copy, modify, or
|
||||
redistribute.
|
||||
*/
|
||||
|
||||
/*jslint evil: true, strict: false */
|
||||
|
||||
/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
|
||||
call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
|
||||
getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
|
||||
lastIndex, length, parse, prototype, push, replace, slice, stringify,
|
||||
test, toJSON, toString, valueOf
|
||||
*/
|
||||
|
||||
|
||||
// Create a JSON object only if one does not already exist. We create the
|
||||
// methods in a closure to avoid creating global variables.
|
||||
|
||||
if (!this.JSON) {
|
||||
this.JSON = {};
|
||||
}
|
||||
|
||||
(function () {
|
||||
|
||||
function f(n) {
|
||||
// Format integers to have at least two digits.
|
||||
return n < 10 ? '0' + n : n;
|
||||
}
|
||||
|
||||
if (typeof Date.prototype.toJSON !== 'function') {
|
||||
|
||||
Date.prototype.toJSON = function (key) {
|
||||
|
||||
return isFinite(this.valueOf()) ?
|
||||
this.getUTCFullYear() + '-' +
|
||||
f(this.getUTCMonth() + 1) + '-' +
|
||||
f(this.getUTCDate()) + 'T' +
|
||||
f(this.getUTCHours()) + ':' +
|
||||
f(this.getUTCMinutes()) + ':' +
|
||||
f(this.getUTCSeconds()) + 'Z' : null;
|
||||
};
|
||||
|
||||
String.prototype.toJSON =
|
||||
Number.prototype.toJSON =
|
||||
Boolean.prototype.toJSON = function (key) {
|
||||
return this.valueOf();
|
||||
};
|
||||
}
|
||||
|
||||
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
|
||||
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
|
||||
gap,
|
||||
indent,
|
||||
meta = { // table of character substitutions
|
||||
'\b': '\\b',
|
||||
'\t': '\\t',
|
||||
'\n': '\\n',
|
||||
'\f': '\\f',
|
||||
'\r': '\\r',
|
||||
'"' : '\\"',
|
||||
'\\': '\\\\'
|
||||
},
|
||||
rep;
|
||||
|
||||
|
||||
function quote(string) {
|
||||
|
||||
// If the string contains no control characters, no quote characters, and no
|
||||
// backslash characters, then we can safely slap some quotes around it.
|
||||
// Otherwise we must also replace the offending characters with safe escape
|
||||
// sequences.
|
||||
|
||||
escapable.lastIndex = 0;
|
||||
return escapable.test(string) ?
|
||||
'"' + string.replace(escapable, function (a) {
|
||||
var c = meta[a];
|
||||
return typeof c === 'string' ? c :
|
||||
'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
|
||||
}) + '"' :
|
||||
'"' + string + '"';
|
||||
}
|
||||
|
||||
|
||||
function str(key, holder) {
|
||||
|
||||
// Produce a string from holder[key].
|
||||
|
||||
var i, // The loop counter.
|
||||
k, // The member key.
|
||||
v, // The member value.
|
||||
length,
|
||||
mind = gap,
|
||||
partial,
|
||||
value = holder[key];
|
||||
|
||||
// If the value has a toJSON method, call it to obtain a replacement value.
|
||||
|
||||
if (value && typeof value === 'object' &&
|
||||
typeof value.toJSON === 'function') {
|
||||
value = value.toJSON(key);
|
||||
}
|
||||
|
||||
// If we were called with a replacer function, then call the replacer to
|
||||
// obtain a replacement value.
|
||||
|
||||
if (typeof rep === 'function') {
|
||||
value = rep.call(holder, key, value);
|
||||
}
|
||||
|
||||
// What happens next depends on the value's type.
|
||||
|
||||
switch (typeof value) {
|
||||
case 'string':
|
||||
return quote(value);
|
||||
|
||||
case 'number':
|
||||
|
||||
// JSON numbers must be finite. Encode non-finite numbers as null.
|
||||
|
||||
return isFinite(value) ? String(value) : 'null';
|
||||
|
||||
case 'boolean':
|
||||
case 'null':
|
||||
|
||||
// If the value is a boolean or null, convert it to a string. Note:
|
||||
// typeof null does not produce 'null'. The case is included here in
|
||||
// the remote chance that this gets fixed someday.
|
||||
|
||||
return String(value);
|
||||
|
||||
// If the type is 'object', we might be dealing with an object or an array or
|
||||
// null.
|
||||
|
||||
case 'object':
|
||||
|
||||
// Due to a specification blunder in ECMAScript, typeof null is 'object',
|
||||
// so watch out for that case.
|
||||
|
||||
if (!value) {
|
||||
return 'null';
|
||||
}
|
||||
|
||||
// Make an array to hold the partial results of stringifying this object value.
|
||||
|
||||
gap += indent;
|
||||
partial = [];
|
||||
|
||||
// Is the value an array?
|
||||
|
||||
if (Object.prototype.toString.apply(value) === '[object Array]') {
|
||||
|
||||
// The value is an array. Stringify every element. Use null as a placeholder
|
||||
// for non-JSON values.
|
||||
|
||||
length = value.length;
|
||||
for (i = 0; i < length; i += 1) {
|
||||
partial[i] = str(i, value) || 'null';
|
||||
}
|
||||
|
||||
// Join all of the elements together, separated with commas, and wrap them in
|
||||
// brackets.
|
||||
|
||||
v = partial.length === 0 ? '[]' :
|
||||
gap ? '[\n' + gap +
|
||||
partial.join(',\n' + gap) + '\n' +
|
||||
mind + ']' :
|
||||
'[' + partial.join(',') + ']';
|
||||
gap = mind;
|
||||
return v;
|
||||
}
|
||||
|
||||
// If the replacer is an array, use it to select the members to be stringified.
|
||||
|
||||
if (rep && typeof rep === 'object') {
|
||||
length = rep.length;
|
||||
for (i = 0; i < length; i += 1) {
|
||||
k = rep[i];
|
||||
if (typeof k === 'string') {
|
||||
v = str(k, value);
|
||||
if (v) {
|
||||
partial.push(quote(k) + (gap ? ': ' : ':') + v);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
// Otherwise, iterate through all of the keys in the object.
|
||||
|
||||
for (k in value) {
|
||||
if (Object.hasOwnProperty.call(value, k)) {
|
||||
v = str(k, value);
|
||||
if (v) {
|
||||
partial.push(quote(k) + (gap ? ': ' : ':') + v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Join all of the member texts together, separated with commas,
|
||||
// and wrap them in braces.
|
||||
|
||||
v = partial.length === 0 ? '{}' :
|
||||
gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
|
||||
mind + '}' : '{' + partial.join(',') + '}';
|
||||
gap = mind;
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
// If the JSON object does not yet have a stringify method, give it one.
|
||||
|
||||
if (typeof JSON.stringify !== 'function') {
|
||||
JSON.stringify = function (value, replacer, space) {
|
||||
|
||||
// The stringify method takes a value and an optional replacer, and an optional
|
||||
// space parameter, and returns a JSON text. The replacer can be a function
|
||||
// that can replace values, or an array of strings that will select the keys.
|
||||
// A default replacer method can be provided. Use of the space parameter can
|
||||
// produce text that is more easily readable.
|
||||
|
||||
var i;
|
||||
gap = '';
|
||||
indent = '';
|
||||
|
||||
// If the space parameter is a number, make an indent string containing that
|
||||
// many spaces.
|
||||
|
||||
if (typeof space === 'number') {
|
||||
for (i = 0; i < space; i += 1) {
|
||||
indent += ' ';
|
||||
}
|
||||
|
||||
// If the space parameter is a string, it will be used as the indent string.
|
||||
|
||||
} else if (typeof space === 'string') {
|
||||
indent = space;
|
||||
}
|
||||
|
||||
// If there is a replacer, it must be a function or an array.
|
||||
// Otherwise, throw an error.
|
||||
|
||||
rep = replacer;
|
||||
if (replacer && typeof replacer !== 'function' &&
|
||||
(typeof replacer !== 'object' ||
|
||||
typeof replacer.length !== 'number')) {
|
||||
throw new Error('JSON.stringify');
|
||||
}
|
||||
|
||||
// Make a fake root object containing our value under the key of ''.
|
||||
// Return the result of stringifying the value.
|
||||
|
||||
return str('', {'': value});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// If the JSON object does not yet have a parse method, give it one.
|
||||
|
||||
if (typeof JSON.parse !== 'function') {
|
||||
JSON.parse = function (text, reviver) {
|
||||
|
||||
// The parse method takes a text and an optional reviver function, and returns
|
||||
// a JavaScript value if the text is a valid JSON text.
|
||||
|
||||
var j;
|
||||
|
||||
function walk(holder, key) {
|
||||
|
||||
// The walk method is used to recursively walk the resulting structure so
|
||||
// that modifications can be made.
|
||||
|
||||
var k, v, value = holder[key];
|
||||
if (value && typeof value === 'object') {
|
||||
for (k in value) {
|
||||
if (Object.hasOwnProperty.call(value, k)) {
|
||||
v = walk(value, k);
|
||||
if (v !== undefined) {
|
||||
value[k] = v;
|
||||
} else {
|
||||
delete value[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return reviver.call(holder, key, value);
|
||||
}
|
||||
|
||||
|
||||
// Parsing happens in four stages. In the first stage, we replace certain
|
||||
// Unicode characters with escape sequences. JavaScript handles many characters
|
||||
// incorrectly, either silently deleting them, or treating them as line endings.
|
||||
|
||||
cx.lastIndex = 0;
|
||||
if (cx.test(text)) {
|
||||
text = text.replace(cx, function (a) {
|
||||
return '\\u' +
|
||||
('0000' + a.charCodeAt(0).toString(16)).slice(-4);
|
||||
});
|
||||
}
|
||||
|
||||
// In the second stage, we run the text against regular expressions that look
|
||||
// for non-JSON patterns. We are especially concerned with '()' and 'new'
|
||||
// because they can cause invocation, and '=' because it can cause mutation.
|
||||
// But just to be safe, we want to reject all unexpected forms.
|
||||
|
||||
// We split the second stage into 4 regexp operations in order to work around
|
||||
// crippling inefficiencies in IE's and Safari's regexp engines. First we
|
||||
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
|
||||
// replace all simple value tokens with ']' characters. Third, we delete all
|
||||
// open brackets that follow a colon or comma or that begin the text. Finally,
|
||||
// we look to see that the remaining characters are only whitespace or ']' or
|
||||
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
|
||||
|
||||
if (/^[\],:{}\s]*$/.
|
||||
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
|
||||
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
|
||||
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
|
||||
|
||||
// In the third stage we use the eval function to compile the text into a
|
||||
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
|
||||
// in JavaScript: it can begin a block or an object literal. We wrap the text
|
||||
// in parens to eliminate the ambiguity.
|
||||
|
||||
j = eval('(' + text + ')');
|
||||
|
||||
// In the optional fourth stage, we recursively walk the new structure, passing
|
||||
// each name/value pair to a reviver function for possible transformation.
|
||||
|
||||
return typeof reviver === 'function' ?
|
||||
walk({'': j}, '') : j;
|
||||
}
|
||||
|
||||
// If the text is not JSON parseable, then a SyntaxError is thrown.
|
||||
|
||||
throw new SyntaxError('JSON.parse');
|
||||
};
|
||||
}
|
||||
}());
|
||||
20
vendor/json3/LICENSE
vendored
Normal file
20
vendor/json3/LICENSE
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
Copyright (c) 2012 Kit Cambridge.
|
||||
http://kitcambridge.github.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
124
vendor/json3/README.md
vendored
Normal file
124
vendor/json3/README.md
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
# JSON 3 #
|
||||
|
||||

|
||||
|
||||
**JSON 3** is a modern JSON implementation compatible with a variety of JavaScript platforms, including Internet Explorer 6, Opera 7, Safari 2, and Netscape 6. The current version is **3.2.4**.
|
||||
|
||||
- [Development Version](http://bestiejs.github.com/json3/lib/json3.js) *(36.5 KB; uncompressed with comments)*
|
||||
- [Production Version](http://bestiejs.github.com/json3/lib/json3.min.js) *(3.0 KB; compressed and `gzip`-ped)*
|
||||
|
||||
[JSON](http://json.org/) is a language-independent data interchange format based on a loose subset of the JavaScript grammar. Originally popularized by [Douglas Crockford](http://www.crockford.com/), the format was standardized in the [fifth edition](http://es5.github.com/) of the ECMAScript specification. The 5.1 edition, ratified in June 2011, incorporates several modifications to the grammar pertaining to the serialization of dates.
|
||||
|
||||
JSON 3 exposes two functions: `stringify()` for [serializing](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/JSON/stringify) a JavaScript value to JSON, and `parse()` for [producing](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/JSON/parse) a JavaScript value from a JSON source string. It is a **drop-in replacement** for [JSON 2](http://json.org/js). The functions behave exactly as described in the ECMAScript spec, **except** for the date serialization discrepancy noted below.
|
||||
|
||||
The JSON 3 parser does **not** use `eval` or regular expressions. This provides security and performance benefits in obsolete and mobile environments, where the margin is particularly significant. The complete [benchmark suite](http://jsperf.com/json3) is available on [jsPerf](http://jsperf.com/).
|
||||
|
||||
The project is [hosted on GitHub](http://git.io/json3), along with the [unit tests](http://bestiejs.github.com/json3/test/test_browser.html). It is part of the [BestieJS](https://github.com/bestiejs) family, a collection of best-in-class JavaScript libraries that promote cross-platform support, specification precedents, unit testing, and plenty of documentation.
|
||||
|
||||
# Changes from JSON 2 #
|
||||
|
||||
JSON 3...
|
||||
|
||||
* Correctly serializes primitive wrapper objects.
|
||||
* Throws a `TypeError` when serializing cyclic structures (JSON 2 recurses until the call stack overflows).
|
||||
* Utilizes **feature tests** to detect broken or incomplete *native* JSON implementations (JSON 2 only checks for the presence of the native functions). The tests are only executed once at runtime, so there is no additional performance cost when parsing or serializing values.
|
||||
|
||||
**As of v3.2.3**, JSON 3 is compatible with [Prototype](http://prototypejs.org) 1.6.1 and older.
|
||||
|
||||
In contrast to JSON 2, JSON 3 **does not**...
|
||||
|
||||
* Add `toJSON()` methods to the `Boolean`, `Number`, and `String` prototypes. These are not part of any standard, and are made redundant by the design of the `stringify()` implementation.
|
||||
* Add `toJSON()` or `toISOString()` methods to `Date.prototype`. See the note about date serialization below.
|
||||
|
||||
## Date Serialization
|
||||
|
||||
**JSON 3 deviates from the specification in one important way**: it does not define `Date#toISOString()` or `Date#toJSON()`. This preserves CommonJS compatibility and avoids polluting native prototypes. Instead, date serialization is performed internally by the `stringify()` implementation: if a date object does not define a custom `toJSON()` method, it is serialized as a [simplified ISO 8601 date-time string](http://es5.github.com/#x15.9.1.15).
|
||||
|
||||
**Several native `Date#toJSON()` implementations produce date time strings that do *not* conform to the grammar outlined in the spec**. For instance, all versions of Safari 4, as well as JSON 2, fail to serialize extended years correctly. Furthermore, JSON 2 and older implementations omit the milliseconds from the date-time string (optional in ES 5, but required in 5.1). Finally, in all versions of Safari 4 and 5, serializing an invalid date will produce the string `"Invalid Date"`, rather than `null`. Because these environments exhibit other serialization bugs, however, JSON 3 will override the native `stringify()` implementation.
|
||||
|
||||
Portions of the date serialization code are adapted from the [`date-shim`](https://github.com/Yaffle/date-shim) project.
|
||||
|
||||
# Usage #
|
||||
|
||||
## Web Browsers
|
||||
|
||||
<script src="http://bestiejs.github.com/json3/lib/json3.min.js"></script>
|
||||
<script>
|
||||
JSON.stringify({"Hello": 123});
|
||||
// => '{"Hello":123}'
|
||||
JSON.parse("[[1, 2, 3], 1, 2, 3, 4]", function (key, value) {
|
||||
if (typeof value == "number") {
|
||||
value = value % 2 ? "Odd" : "Even";
|
||||
}
|
||||
return value;
|
||||
});
|
||||
// => [["Odd", "Even", "Odd"], "Odd", "Even", "Odd", "Even"]
|
||||
</script>
|
||||
|
||||
## CommonJS Environments
|
||||
|
||||
var JSON3 = require("./path/to/json3");
|
||||
JSON3.parse("[1, 2, 3]");
|
||||
// => [1, 2, 3]
|
||||
|
||||
## JavaScript Engines
|
||||
|
||||
load("path/to/json3.js");
|
||||
JSON.stringify({"Hello": 123, "Good-bye": 456}, ["Hello"], "\t");
|
||||
// => '{\n\t"Hello": 123\n}'
|
||||
|
||||
# Compatibility #
|
||||
|
||||
JSON 3 has been **tested** with the following web browsers, CommonJS environments, and JavaScript engines.
|
||||
|
||||
## Web Browsers
|
||||
|
||||
- Windows [Internet Explorer](http://www.microsoft.com/windows/internet-explorer), version 6.0 and higher
|
||||
- Mozilla [Firefox](http://www.mozilla.com/firefox), version 1.0 and higher
|
||||
- Apple [Safari](http://www.apple.com/safari), version 2.0 and higher
|
||||
- [Opera](http://www.opera.com) 7.02 and higher
|
||||
- [Mozilla](http://sillydog.org/narchive/gecko.php) 1.0, [Netscape](http://sillydog.org/narchive/) 6.2.3, and [SeaMonkey](http://www.seamonkey-project.org/) 1.0 and higher
|
||||
|
||||
## CommonJS Environments
|
||||
|
||||
- [Node](http://nodejs.org/) 0.2.6 and higher
|
||||
- [RingoJS](http://ringojs.org/) 0.4 and higher
|
||||
- [Narwhal](http://narwhaljs.org/) 0.3.2 and higher
|
||||
|
||||
## JavaScript Engines
|
||||
|
||||
- Mozilla [Rhino](http://www.mozilla.org/rhino) 1.5R5 and higher
|
||||
- WebKit [JSC](https://trac.webkit.org/wiki/JSC)
|
||||
- Google [V8](http://code.google.com/p/v8)
|
||||
|
||||
## Known Incompatibilities
|
||||
|
||||
* Attempting to serialize the `arguments` object may produce inconsistent results across environments due to specification version differences. As a workaround, please convert the `arguments` object to an array first: `JSON.stringify([].slice.call(arguments, 0))`.
|
||||
|
||||
## Required Native Methods
|
||||
|
||||
JSON 3 assumes that the following methods exist and function as described in the ECMAScript specification:
|
||||
|
||||
- The `Number`, `String`, `Array`, `Object`, `Date`, `SyntaxError`, and `TypeError` constructors.
|
||||
- `String.fromCharCode`
|
||||
- `Object#toString`
|
||||
- `Function#call`
|
||||
- `Math.floor`
|
||||
- `Number#toString`
|
||||
- `Date#valueOf`
|
||||
- `String.prototype`: `indexOf`, `charCodeAt`, `charAt`, `slice`.
|
||||
- `Array.prototype`: `push`, `pop`, `join`.
|
||||
|
||||
# Contribute #
|
||||
|
||||
Check out a working copy of the JSON 3 source code with [Git](http://git-scm.com/):
|
||||
|
||||
$ git clone git://github.com/bestiejs/json3.git
|
||||
$ cd json3
|
||||
$ git submodule update --init
|
||||
|
||||
If you'd like to contribute a feature or bug fix, you can [fork](http://help.github.com/fork-a-repo/) JSON 3, commit your changes, and [send a pull request](http://help.github.com/send-pull-requests/). Please make sure to update the unit tests in the `test` directory as well.
|
||||
|
||||
Alternatively, you can use the [GitHub issue tracker](https://github.com/bestiejs/json3/issues) to submit bug reports, feature requests, and questions, or send tweets to [@kitcambridge](http://twitter.com/kitcambridge).
|
||||
|
||||
JSON 3 is released under the [MIT License](http://kit.mit-license.org/).
|
||||
783
vendor/json3/lib/json3.js
vendored
Normal file
783
vendor/json3/lib/json3.js
vendored
Normal file
@@ -0,0 +1,783 @@
|
||||
/*! JSON v3.2.4 | http://bestiejs.github.com/json3 | Copyright 2012, Kit Cambridge | http://kit.mit-license.org */
|
||||
;(function () {
|
||||
// Convenience aliases.
|
||||
var getClass = {}.toString, isProperty, forEach, undef;
|
||||
|
||||
// Detect the `define` function exposed by asynchronous module loaders. The
|
||||
// strict `define` check is necessary for compatibility with `r.js`.
|
||||
var isLoader = typeof define === "function" && define.amd, JSON3 = !isLoader && typeof exports == "object" && exports;
|
||||
|
||||
if (JSON3 || isLoader) {
|
||||
if (typeof JSON == "object" && JSON) {
|
||||
// Delegate to the native `stringify` and `parse` implementations in
|
||||
// asynchronous module loaders and CommonJS environments.
|
||||
if (isLoader) {
|
||||
JSON3 = JSON;
|
||||
} else {
|
||||
JSON3.stringify = JSON.stringify;
|
||||
JSON3.parse = JSON.parse;
|
||||
}
|
||||
} else if (isLoader) {
|
||||
JSON3 = this.JSON = {};
|
||||
}
|
||||
} else {
|
||||
// Export for web browsers and JavaScript engines.
|
||||
JSON3 = this.JSON || (this.JSON = {});
|
||||
}
|
||||
|
||||
// Local variables.
|
||||
var Escapes, toPaddedString, quote, serialize;
|
||||
var fromCharCode, Unescapes, abort, lex, get, walk, update, Index, Source;
|
||||
|
||||
// Test the `Date#getUTC*` methods. Based on work by @Yaffle.
|
||||
var isExtended = new Date(-3509827334573292), floor, Months, getDay;
|
||||
|
||||
try {
|
||||
// The `getUTCFullYear`, `Month`, and `Date` methods return nonsensical
|
||||
// results for certain dates in Opera >= 10.53.
|
||||
isExtended = isExtended.getUTCFullYear() == -109252 && isExtended.getUTCMonth() === 0 && isExtended.getUTCDate() == 1 &&
|
||||
// Safari < 2.0.2 stores the internal millisecond time value correctly,
|
||||
// but clips the values returned by the date methods to the range of
|
||||
// signed 32-bit integers ([-2 ** 31, 2 ** 31 - 1]).
|
||||
isExtended.getUTCHours() == 10 && isExtended.getUTCMinutes() == 37 && isExtended.getUTCSeconds() == 6 && isExtended.getUTCMilliseconds() == 708;
|
||||
} catch (exception) {}
|
||||
|
||||
// Internal: Determines whether the native `JSON.stringify` and `parse`
|
||||
// implementations are spec-compliant. Based on work by Ken Snyder.
|
||||
function has(name) {
|
||||
var stringifySupported, parseSupported, value, serialized = '{"A":[1,true,false,null,"\\u0000\\b\\n\\f\\r\\t"]}', all = name == "json";
|
||||
if (all || name == "json-stringify" || name == "json-parse") {
|
||||
// Test `JSON.stringify`.
|
||||
if (name == "json-stringify" || all) {
|
||||
if ((stringifySupported = typeof JSON3.stringify == "function" && isExtended)) {
|
||||
// A test function object with a custom `toJSON` method.
|
||||
(value = function () {
|
||||
return 1;
|
||||
}).toJSON = value;
|
||||
try {
|
||||
stringifySupported =
|
||||
// Firefox 3.1b1 and b2 serialize string, number, and boolean
|
||||
// primitives as object literals.
|
||||
JSON3.stringify(0) === "0" &&
|
||||
// FF 3.1b1, b2, and JSON 2 serialize wrapped primitives as object
|
||||
// literals.
|
||||
JSON3.stringify(new Number()) === "0" &&
|
||||
JSON3.stringify(new String()) == '""' &&
|
||||
// FF 3.1b1, 2 throw an error if the value is `null`, `undefined`, or
|
||||
// does not define a canonical JSON representation (this applies to
|
||||
// objects with `toJSON` properties as well, *unless* they are nested
|
||||
// within an object or array).
|
||||
JSON3.stringify(getClass) === undef &&
|
||||
// IE 8 serializes `undefined` as `"undefined"`. Safari <= 5.1.7 and
|
||||
// FF 3.1b3 pass this test.
|
||||
JSON3.stringify(undef) === undef &&
|
||||
// Safari <= 5.1.7 and FF 3.1b3 throw `Error`s and `TypeError`s,
|
||||
// respectively, if the value is omitted entirely.
|
||||
JSON3.stringify() === undef &&
|
||||
// FF 3.1b1, 2 throw an error if the given value is not a number,
|
||||
// string, array, object, Boolean, or `null` literal. This applies to
|
||||
// objects with custom `toJSON` methods as well, unless they are nested
|
||||
// inside object or array literals. YUI 3.0.0b1 ignores custom `toJSON`
|
||||
// methods entirely.
|
||||
JSON3.stringify(value) === "1" &&
|
||||
JSON3.stringify([value]) == "[1]" &&
|
||||
// Prototype <= 1.6.1 serializes `[undefined]` as `"[]"` instead of
|
||||
// `"[null]"`.
|
||||
JSON3.stringify([undef]) == "[null]" &&
|
||||
// YUI 3.0.0b1 fails to serialize `null` literals.
|
||||
JSON3.stringify(null) == "null" &&
|
||||
// FF 3.1b1, 2 halts serialization if an array contains a function:
|
||||
// `[1, true, getClass, 1]` serializes as "[1,true,],". These versions
|
||||
// of Firefox also allow trailing commas in JSON objects and arrays.
|
||||
// FF 3.1b3 elides non-JSON values from objects and arrays, unless they
|
||||
// define custom `toJSON` methods.
|
||||
JSON3.stringify([undef, getClass, null]) == "[null,null,null]" &&
|
||||
// Simple serialization test. FF 3.1b1 uses Unicode escape sequences
|
||||
// where character escape codes are expected (e.g., `\b` => `\u0008`).
|
||||
JSON3.stringify({ "A": [value, true, false, null, "\0\b\n\f\r\t"] }) == serialized &&
|
||||
// FF 3.1b1 and b2 ignore the `filter` and `width` arguments.
|
||||
JSON3.stringify(null, value) === "1" &&
|
||||
JSON3.stringify([1, 2], null, 1) == "[\n 1,\n 2\n]" &&
|
||||
// JSON 2, Prototype <= 1.7, and older WebKit builds incorrectly
|
||||
// serialize extended years.
|
||||
JSON3.stringify(new Date(-8.64e15)) == '"-271821-04-20T00:00:00.000Z"' &&
|
||||
// The milliseconds are optional in ES 5, but required in 5.1.
|
||||
JSON3.stringify(new Date(8.64e15)) == '"+275760-09-13T00:00:00.000Z"' &&
|
||||
// Firefox <= 11.0 incorrectly serializes years prior to 0 as negative
|
||||
// four-digit years instead of six-digit years. Credits: @Yaffle.
|
||||
JSON3.stringify(new Date(-621987552e5)) == '"-000001-01-01T00:00:00.000Z"' &&
|
||||
// Safari <= 5.1.5 and Opera >= 10.53 incorrectly serialize millisecond
|
||||
// values less than 1000. Credits: @Yaffle.
|
||||
JSON3.stringify(new Date(-1)) == '"1969-12-31T23:59:59.999Z"';
|
||||
} catch (exception) {
|
||||
stringifySupported = false;
|
||||
}
|
||||
}
|
||||
if (!all) {
|
||||
return stringifySupported;
|
||||
}
|
||||
}
|
||||
// Test `JSON.parse`.
|
||||
if (name == "json-parse" || all) {
|
||||
if (typeof JSON3.parse == "function") {
|
||||
try {
|
||||
// FF 3.1b1, b2 will throw an exception if a bare literal is provided.
|
||||
// Conforming implementations should also coerce the initial argument to
|
||||
// a string prior to parsing.
|
||||
if (JSON3.parse("0") === 0 && !JSON3.parse(false)) {
|
||||
// Simple parsing test.
|
||||
value = JSON3.parse(serialized);
|
||||
if ((parseSupported = value.A.length == 5 && value.A[0] == 1)) {
|
||||
try {
|
||||
// Safari <= 5.1.2 and FF 3.1b1 allow unescaped tabs in strings.
|
||||
parseSupported = !JSON3.parse('"\t"');
|
||||
} catch (exception) {}
|
||||
if (parseSupported) {
|
||||
try {
|
||||
// FF 4.0 and 4.0.1 allow leading `+` signs, and leading and
|
||||
// trailing decimal points. FF 4.0, 4.0.1, and IE 9-10 also
|
||||
// allow certain octal literals.
|
||||
parseSupported = JSON3.parse("01") != 1;
|
||||
} catch (exception) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (exception) {
|
||||
parseSupported = false;
|
||||
}
|
||||
}
|
||||
if (!all) {
|
||||
return parseSupported;
|
||||
}
|
||||
}
|
||||
return stringifySupported && parseSupported;
|
||||
}
|
||||
}
|
||||
|
||||
if (!has("json")) {
|
||||
// Define additional utility methods if the `Date` methods are buggy.
|
||||
if (!isExtended) {
|
||||
floor = Math.floor;
|
||||
// A mapping between the months of the year and the number of days between
|
||||
// January 1st and the first of the respective month.
|
||||
Months = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
|
||||
// Internal: Calculates the number of days between the Unix epoch and the
|
||||
// first day of the given month.
|
||||
getDay = function (year, month) {
|
||||
return Months[month] + 365 * (year - 1970) + floor((year - 1969 + (month = +(month > 1))) / 4) - floor((year - 1901 + month) / 100) + floor((year - 1601 + month) / 400);
|
||||
};
|
||||
}
|
||||
|
||||
// Internal: Determines if a property is a direct property of the given
|
||||
// object. Delegates to the native `Object#hasOwnProperty` method.
|
||||
if (!(isProperty = {}.hasOwnProperty)) {
|
||||
isProperty = function (property) {
|
||||
var members = {}, constructor;
|
||||
if ((members.__proto__ = null, members.__proto__ = {
|
||||
// The *proto* property cannot be set multiple times in recent
|
||||
// versions of Firefox and SeaMonkey.
|
||||
"toString": 1
|
||||
}, members).toString != getClass) {
|
||||
// Safari <= 2.0.3 doesn't implement `Object#hasOwnProperty`, but
|
||||
// supports the mutable *proto* property.
|
||||
isProperty = function (property) {
|
||||
// Capture and break the object's prototype chain (see section 8.6.2
|
||||
// of the ES 5.1 spec). The parenthesized expression prevents an
|
||||
// unsafe transformation by the Closure Compiler.
|
||||
var original = this.__proto__, result = property in (this.__proto__ = null, this);
|
||||
// Restore the original prototype chain.
|
||||
this.__proto__ = original;
|
||||
return result;
|
||||
};
|
||||
} else {
|
||||
// Capture a reference to the top-level `Object` constructor.
|
||||
constructor = members.constructor;
|
||||
// Use the `constructor` property to simulate `Object#hasOwnProperty` in
|
||||
// other environments.
|
||||
isProperty = function (property) {
|
||||
var parent = (this.constructor || constructor).prototype;
|
||||
return property in this && !(property in parent && this[property] === parent[property]);
|
||||
};
|
||||
}
|
||||
members = null;
|
||||
return isProperty.call(this, property);
|
||||
};
|
||||
}
|
||||
|
||||
// Internal: Normalizes the `for...in` iteration algorithm across
|
||||
// environments. Each enumerated key is yielded to a `callback` function.
|
||||
forEach = function (object, callback) {
|
||||
var size = 0, Properties, members, property, forEach;
|
||||
|
||||
// Tests for bugs in the current environment's `for...in` algorithm. The
|
||||
// `valueOf` property inherits the non-enumerable flag from
|
||||
// `Object.prototype` in older versions of IE, Netscape, and Mozilla.
|
||||
(Properties = function () {
|
||||
this.valueOf = 0;
|
||||
}).prototype.valueOf = 0;
|
||||
|
||||
// Iterate over a new instance of the `Properties` class.
|
||||
members = new Properties();
|
||||
for (property in members) {
|
||||
// Ignore all properties inherited from `Object.prototype`.
|
||||
if (isProperty.call(members, property)) {
|
||||
size++;
|
||||
}
|
||||
}
|
||||
Properties = members = null;
|
||||
|
||||
// Normalize the iteration algorithm.
|
||||
if (!size) {
|
||||
// A list of non-enumerable properties inherited from `Object.prototype`.
|
||||
members = ["valueOf", "toString", "toLocaleString", "propertyIsEnumerable", "isPrototypeOf", "hasOwnProperty", "constructor"];
|
||||
// IE <= 8, Mozilla 1.0, and Netscape 6.2 ignore shadowed non-enumerable
|
||||
// properties.
|
||||
forEach = function (object, callback) {
|
||||
var isFunction = getClass.call(object) == "[object Function]", property, length;
|
||||
for (property in object) {
|
||||
// Gecko <= 1.0 enumerates the `prototype` property of functions under
|
||||
// certain conditions; IE does not.
|
||||
if (!(isFunction && property == "prototype") && isProperty.call(object, property)) {
|
||||
callback(property);
|
||||
}
|
||||
}
|
||||
// Manually invoke the callback for each non-enumerable property.
|
||||
for (length = members.length; property = members[--length]; isProperty.call(object, property) && callback(property));
|
||||
};
|
||||
} else if (size == 2) {
|
||||
// Safari <= 2.0.4 enumerates shadowed properties twice.
|
||||
forEach = function (object, callback) {
|
||||
// Create a set of iterated properties.
|
||||
var members = {}, isFunction = getClass.call(object) == "[object Function]", property;
|
||||
for (property in object) {
|
||||
// Store each property name to prevent double enumeration. The
|
||||
// `prototype` property of functions is not enumerated due to cross-
|
||||
// environment inconsistencies.
|
||||
if (!(isFunction && property == "prototype") && !isProperty.call(members, property) && (members[property] = 1) && isProperty.call(object, property)) {
|
||||
callback(property);
|
||||
}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// No bugs detected; use the standard `for...in` algorithm.
|
||||
forEach = function (object, callback) {
|
||||
var isFunction = getClass.call(object) == "[object Function]", property, isConstructor;
|
||||
for (property in object) {
|
||||
if (!(isFunction && property == "prototype") && isProperty.call(object, property) && !(isConstructor = property === "constructor")) {
|
||||
callback(property);
|
||||
}
|
||||
}
|
||||
// Manually invoke the callback for the `constructor` property due to
|
||||
// cross-environment inconsistencies.
|
||||
if (isConstructor || isProperty.call(object, (property = "constructor"))) {
|
||||
callback(property);
|
||||
}
|
||||
};
|
||||
}
|
||||
return forEach(object, callback);
|
||||
};
|
||||
|
||||
// Public: Serializes a JavaScript `value` as a JSON string. The optional
|
||||
// `filter` argument may specify either a function that alters how object and
|
||||
// array members are serialized, or an array of strings and numbers that
|
||||
// indicates which properties should be serialized. The optional `width`
|
||||
// argument may be either a string or number that specifies the indentation
|
||||
// level of the output.
|
||||
if (!has("json-stringify")) {
|
||||
// Internal: A map of control characters and their escaped equivalents.
|
||||
Escapes = {
|
||||
"\\": "\\\\",
|
||||
'"': '\\"',
|
||||
"\b": "\\b",
|
||||
"\f": "\\f",
|
||||
"\n": "\\n",
|
||||
"\r": "\\r",
|
||||
"\t": "\\t"
|
||||
};
|
||||
|
||||
// Internal: Converts `value` into a zero-padded string such that its
|
||||
// length is at least equal to `width`. The `width` must be <= 6.
|
||||
toPaddedString = function (width, value) {
|
||||
// The `|| 0` expression is necessary to work around a bug in
|
||||
// Opera <= 7.54u2 where `0 == -0`, but `String(-0) !== "0"`.
|
||||
return ("000000" + (value || 0)).slice(-width);
|
||||
};
|
||||
|
||||
// Internal: Double-quotes a string `value`, replacing all ASCII control
|
||||
// characters (characters with code unit values between 0 and 31) with
|
||||
// their escaped equivalents. This is an implementation of the
|
||||
// `Quote(value)` operation defined in ES 5.1 section 15.12.3.
|
||||
quote = function (value) {
|
||||
var result = '"', index = 0, symbol;
|
||||
for (; symbol = value.charAt(index); index++) {
|
||||
// Escape the reverse solidus, double quote, backspace, form feed, line
|
||||
// feed, carriage return, and tab characters.
|
||||
result += '\\"\b\f\n\r\t'.indexOf(symbol) > -1 ? Escapes[symbol] :
|
||||
// If the character is a control character, append its Unicode escape
|
||||
// sequence; otherwise, append the character as-is.
|
||||
(Escapes[symbol] = symbol < " " ? "\\u00" + toPaddedString(2, symbol.charCodeAt(0).toString(16)) : symbol);
|
||||
}
|
||||
return result + '"';
|
||||
};
|
||||
|
||||
// Internal: Recursively serializes an object. Implements the
|
||||
// `Str(key, holder)`, `JO(value)`, and `JA(value)` operations.
|
||||
serialize = function (property, object, callback, properties, whitespace, indentation, stack) {
|
||||
var value = object[property], className, year, month, date, time, hours, minutes, seconds, milliseconds, results, element, index, length, prefix, any, result;
|
||||
if (typeof value == "object" && value) {
|
||||
className = getClass.call(value);
|
||||
if (className == "[object Date]" && !isProperty.call(value, "toJSON")) {
|
||||
if (value > -1 / 0 && value < 1 / 0) {
|
||||
// Dates are serialized according to the `Date#toJSON` method
|
||||
// specified in ES 5.1 section 15.9.5.44. See section 15.9.1.15
|
||||
// for the ISO 8601 date time string format.
|
||||
if (getDay) {
|
||||
// Manually compute the year, month, date, hours, minutes,
|
||||
// seconds, and milliseconds if the `getUTC*` methods are
|
||||
// buggy. Adapted from @Yaffle's `date-shim` project.
|
||||
date = floor(value / 864e5);
|
||||
for (year = floor(date / 365.2425) + 1970 - 1; getDay(year + 1, 0) <= date; year++);
|
||||
for (month = floor((date - getDay(year, 0)) / 30.42); getDay(year, month + 1) <= date; month++);
|
||||
date = 1 + date - getDay(year, month);
|
||||
// The `time` value specifies the time within the day (see ES
|
||||
// 5.1 section 15.9.1.2). The formula `(A % B + B) % B` is used
|
||||
// to compute `A modulo B`, as the `%` operator does not
|
||||
// correspond to the `modulo` operation for negative numbers.
|
||||
time = (value % 864e5 + 864e5) % 864e5;
|
||||
// The hours, minutes, seconds, and milliseconds are obtained by
|
||||
// decomposing the time within the day. See section 15.9.1.10.
|
||||
hours = floor(time / 36e5) % 24;
|
||||
minutes = floor(time / 6e4) % 60;
|
||||
seconds = floor(time / 1e3) % 60;
|
||||
milliseconds = time % 1e3;
|
||||
} else {
|
||||
year = value.getUTCFullYear();
|
||||
month = value.getUTCMonth();
|
||||
date = value.getUTCDate();
|
||||
hours = value.getUTCHours();
|
||||
minutes = value.getUTCMinutes();
|
||||
seconds = value.getUTCSeconds();
|
||||
milliseconds = value.getUTCMilliseconds();
|
||||
}
|
||||
// Serialize extended years correctly.
|
||||
value = (year <= 0 || year >= 1e4 ? (year < 0 ? "-" : "+") + toPaddedString(6, year < 0 ? -year : year) : toPaddedString(4, year)) +
|
||||
"-" + toPaddedString(2, month + 1) + "-" + toPaddedString(2, date) +
|
||||
// Months, dates, hours, minutes, and seconds should have two
|
||||
// digits; milliseconds should have three.
|
||||
"T" + toPaddedString(2, hours) + ":" + toPaddedString(2, minutes) + ":" + toPaddedString(2, seconds) +
|
||||
// Milliseconds are optional in ES 5.0, but required in 5.1.
|
||||
"." + toPaddedString(3, milliseconds) + "Z";
|
||||
} else {
|
||||
value = null;
|
||||
}
|
||||
} else if (typeof value.toJSON == "function" && ((className != "[object Number]" && className != "[object String]" && className != "[object Array]") || isProperty.call(value, "toJSON"))) {
|
||||
// Prototype <= 1.6.1 adds non-standard `toJSON` methods to the
|
||||
// `Number`, `String`, `Date`, and `Array` prototypes. JSON 3
|
||||
// ignores all `toJSON` methods on these objects unless they are
|
||||
// defined directly on an instance.
|
||||
value = value.toJSON(property);
|
||||
}
|
||||
}
|
||||
if (callback) {
|
||||
// If a replacement function was provided, call it to obtain the value
|
||||
// for serialization.
|
||||
value = callback.call(object, property, value);
|
||||
}
|
||||
if (value === null) {
|
||||
return "null";
|
||||
}
|
||||
className = getClass.call(value);
|
||||
if (className == "[object Boolean]") {
|
||||
// Booleans are represented literally.
|
||||
return "" + value;
|
||||
} else if (className == "[object Number]") {
|
||||
// JSON numbers must be finite. `Infinity` and `NaN` are serialized as
|
||||
// `"null"`.
|
||||
return value > -1 / 0 && value < 1 / 0 ? "" + value : "null";
|
||||
} else if (className == "[object String]") {
|
||||
// Strings are double-quoted and escaped.
|
||||
return quote(value);
|
||||
}
|
||||
// Recursively serialize objects and arrays.
|
||||
if (typeof value == "object") {
|
||||
// Check for cyclic structures. This is a linear search; performance
|
||||
// is inversely proportional to the number of unique nested objects.
|
||||
for (length = stack.length; length--;) {
|
||||
if (stack[length] === value) {
|
||||
// Cyclic structures cannot be serialized by `JSON.stringify`.
|
||||
throw TypeError();
|
||||
}
|
||||
}
|
||||
// Add the object to the stack of traversed objects.
|
||||
stack.push(value);
|
||||
results = [];
|
||||
// Save the current indentation level and indent one additional level.
|
||||
prefix = indentation;
|
||||
indentation += whitespace;
|
||||
if (className == "[object Array]") {
|
||||
// Recursively serialize array elements.
|
||||
for (index = 0, length = value.length; index < length; any || (any = true), index++) {
|
||||
element = serialize(index, value, callback, properties, whitespace, indentation, stack);
|
||||
results.push(element === undef ? "null" : element);
|
||||
}
|
||||
result = any ? (whitespace ? "[\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "]" : ("[" + results.join(",") + "]")) : "[]";
|
||||
} else {
|
||||
// Recursively serialize object members. Members are selected from
|
||||
// either a user-specified list of property names, or the object
|
||||
// itself.
|
||||
forEach(properties || value, function (property) {
|
||||
var element = serialize(property, value, callback, properties, whitespace, indentation, stack);
|
||||
if (element !== undef) {
|
||||
// According to ES 5.1 section 15.12.3: "If `gap` {whitespace}
|
||||
// is not the empty string, let `member` {quote(property) + ":"}
|
||||
// be the concatenation of `member` and the `space` character."
|
||||
// The "`space` character" refers to the literal space
|
||||
// character, not the `space` {width} argument provided to
|
||||
// `JSON.stringify`.
|
||||
results.push(quote(property) + ":" + (whitespace ? " " : "") + element);
|
||||
}
|
||||
any || (any = true);
|
||||
});
|
||||
result = any ? (whitespace ? "{\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "}" : ("{" + results.join(",") + "}")) : "{}";
|
||||
}
|
||||
// Remove the object from the traversed object stack.
|
||||
stack.pop();
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
// Public: `JSON.stringify`. See ES 5.1 section 15.12.3.
|
||||
JSON3.stringify = function (source, filter, width) {
|
||||
var whitespace, callback, properties, index, length, value;
|
||||
if (typeof filter == "function" || typeof filter == "object" && filter) {
|
||||
if (getClass.call(filter) == "[object Function]") {
|
||||
callback = filter;
|
||||
} else if (getClass.call(filter) == "[object Array]") {
|
||||
// Convert the property names array into a makeshift set.
|
||||
properties = {};
|
||||
for (index = 0, length = filter.length; index < length; value = filter[index++], ((getClass.call(value) == "[object String]" || getClass.call(value) == "[object Number]") && (properties[value] = 1)));
|
||||
}
|
||||
}
|
||||
if (width) {
|
||||
if (getClass.call(width) == "[object Number]") {
|
||||
// Convert the `width` to an integer and create a string containing
|
||||
// `width` number of space characters.
|
||||
if ((width -= width % 1) > 0) {
|
||||
for (whitespace = "", width > 10 && (width = 10); whitespace.length < width; whitespace += " ");
|
||||
}
|
||||
} else if (getClass.call(width) == "[object String]") {
|
||||
whitespace = width.length <= 10 ? width : width.slice(0, 10);
|
||||
}
|
||||
}
|
||||
// Opera <= 7.54u2 discards the values associated with empty string keys
|
||||
// (`""`) only if they are used directly within an object member list
|
||||
// (e.g., `!("" in { "": 1})`).
|
||||
return serialize("", (value = {}, value[""] = source, value), callback, properties, whitespace, "", []);
|
||||
};
|
||||
}
|
||||
|
||||
// Public: Parses a JSON source string.
|
||||
if (!has("json-parse")) {
|
||||
fromCharCode = String.fromCharCode;
|
||||
// Internal: A map of escaped control characters and their unescaped
|
||||
// equivalents.
|
||||
Unescapes = {
|
||||
"\\": "\\",
|
||||
'"': '"',
|
||||
"/": "/",
|
||||
"b": "\b",
|
||||
"t": "\t",
|
||||
"n": "\n",
|
||||
"f": "\f",
|
||||
"r": "\r"
|
||||
};
|
||||
|
||||
// Internal: Resets the parser state and throws a `SyntaxError`.
|
||||
abort = function() {
|
||||
Index = Source = null;
|
||||
throw SyntaxError();
|
||||
};
|
||||
|
||||
// Internal: Returns the next token, or `"$"` if the parser has reached
|
||||
// the end of the source string. A token may be a string, number, `null`
|
||||
// literal, or Boolean literal.
|
||||
lex = function () {
|
||||
var source = Source, length = source.length, symbol, value, begin, position, sign;
|
||||
while (Index < length) {
|
||||
symbol = source.charAt(Index);
|
||||
if ("\t\r\n ".indexOf(symbol) > -1) {
|
||||
// Skip whitespace tokens, including tabs, carriage returns, line
|
||||
// feeds, and space characters.
|
||||
Index++;
|
||||
} else if ("{}[]:,".indexOf(symbol) > -1) {
|
||||
// Parse a punctuator token at the current position.
|
||||
Index++;
|
||||
return symbol;
|
||||
} else if (symbol == '"') {
|
||||
// Advance to the next character and parse a JSON string at the
|
||||
// current position. String tokens are prefixed with the sentinel
|
||||
// `@` character to distinguish them from punctuators.
|
||||
for (value = "@", Index++; Index < length;) {
|
||||
symbol = source.charAt(Index);
|
||||
if (symbol < " ") {
|
||||
// Unescaped ASCII control characters are not permitted.
|
||||
abort();
|
||||
} else if (symbol == "\\") {
|
||||
// Parse escaped JSON control characters, `"`, `\`, `/`, and
|
||||
// Unicode escape sequences.
|
||||
symbol = source.charAt(++Index);
|
||||
if ('\\"/btnfr'.indexOf(symbol) > -1) {
|
||||
// Revive escaped control characters.
|
||||
value += Unescapes[symbol];
|
||||
Index++;
|
||||
} else if (symbol == "u") {
|
||||
// Advance to the first character of the escape sequence.
|
||||
begin = ++Index;
|
||||
// Validate the Unicode escape sequence.
|
||||
for (position = Index + 4; Index < position; Index++) {
|
||||
symbol = source.charAt(Index);
|
||||
// A valid sequence comprises four hexdigits that form a
|
||||
// single hexadecimal value.
|
||||
if (!(symbol >= "0" && symbol <= "9" || symbol >= "a" && symbol <= "f" || symbol >= "A" && symbol <= "F")) {
|
||||
// Invalid Unicode escape sequence.
|
||||
abort();
|
||||
}
|
||||
}
|
||||
// Revive the escaped character.
|
||||
value += fromCharCode("0x" + source.slice(begin, Index));
|
||||
} else {
|
||||
// Invalid escape sequence.
|
||||
abort();
|
||||
}
|
||||
} else {
|
||||
if (symbol == '"') {
|
||||
// An unescaped double-quote character marks the end of the
|
||||
// string.
|
||||
break;
|
||||
}
|
||||
// Append the original character as-is.
|
||||
value += symbol;
|
||||
Index++;
|
||||
}
|
||||
}
|
||||
if (source.charAt(Index) == '"') {
|
||||
Index++;
|
||||
// Return the revived string.
|
||||
return value;
|
||||
}
|
||||
// Unterminated string.
|
||||
abort();
|
||||
} else {
|
||||
// Parse numbers and literals.
|
||||
begin = Index;
|
||||
// Advance the scanner's position past the sign, if one is
|
||||
// specified.
|
||||
if (symbol == "-") {
|
||||
sign = true;
|
||||
symbol = source.charAt(++Index);
|
||||
}
|
||||
// Parse an integer or floating-point value.
|
||||
if (symbol >= "0" && symbol <= "9") {
|
||||
// Leading zeroes are interpreted as octal literals.
|
||||
if (symbol == "0" && (symbol = source.charAt(Index + 1), symbol >= "0" && symbol <= "9")) {
|
||||
// Illegal octal literal.
|
||||
abort();
|
||||
}
|
||||
sign = false;
|
||||
// Parse the integer component.
|
||||
for (; Index < length && (symbol = source.charAt(Index), symbol >= "0" && symbol <= "9"); Index++);
|
||||
// Floats cannot contain a leading decimal point; however, this
|
||||
// case is already accounted for by the parser.
|
||||
if (source.charAt(Index) == ".") {
|
||||
position = ++Index;
|
||||
// Parse the decimal component.
|
||||
for (; position < length && (symbol = source.charAt(position), symbol >= "0" && symbol <= "9"); position++);
|
||||
if (position == Index) {
|
||||
// Illegal trailing decimal.
|
||||
abort();
|
||||
}
|
||||
Index = position;
|
||||
}
|
||||
// Parse exponents.
|
||||
symbol = source.charAt(Index);
|
||||
if (symbol == "e" || symbol == "E") {
|
||||
// Skip past the sign following the exponent, if one is
|
||||
// specified.
|
||||
symbol = source.charAt(++Index);
|
||||
if (symbol == "+" || symbol == "-") {
|
||||
Index++;
|
||||
}
|
||||
// Parse the exponential component.
|
||||
for (position = Index; position < length && (symbol = source.charAt(position), symbol >= "0" && symbol <= "9"); position++);
|
||||
if (position == Index) {
|
||||
// Illegal empty exponent.
|
||||
abort();
|
||||
}
|
||||
Index = position;
|
||||
}
|
||||
// Coerce the parsed value to a JavaScript number.
|
||||
return +source.slice(begin, Index);
|
||||
}
|
||||
// A negative sign may only precede numbers.
|
||||
if (sign) {
|
||||
abort();
|
||||
}
|
||||
// `true`, `false`, and `null` literals.
|
||||
if (source.slice(Index, Index + 4) == "true") {
|
||||
Index += 4;
|
||||
return true;
|
||||
} else if (source.slice(Index, Index + 5) == "false") {
|
||||
Index += 5;
|
||||
return false;
|
||||
} else if (source.slice(Index, Index + 4) == "null") {
|
||||
Index += 4;
|
||||
return null;
|
||||
}
|
||||
// Unrecognized token.
|
||||
abort();
|
||||
}
|
||||
}
|
||||
// Return the sentinel `$` character if the parser has reached the end
|
||||
// of the source string.
|
||||
return "$";
|
||||
};
|
||||
|
||||
// Internal: Parses a JSON `value` token.
|
||||
get = function (value) {
|
||||
var results, any, key;
|
||||
if (value == "$") {
|
||||
// Unexpected end of input.
|
||||
abort();
|
||||
}
|
||||
if (typeof value == "string") {
|
||||
if (value.charAt(0) == "@") {
|
||||
// Remove the sentinel `@` character.
|
||||
return value.slice(1);
|
||||
}
|
||||
// Parse object and array literals.
|
||||
if (value == "[") {
|
||||
// Parses a JSON array, returning a new JavaScript array.
|
||||
results = [];
|
||||
for (;; any || (any = true)) {
|
||||
value = lex();
|
||||
// A closing square bracket marks the end of the array literal.
|
||||
if (value == "]") {
|
||||
break;
|
||||
}
|
||||
// If the array literal contains elements, the current token
|
||||
// should be a comma separating the previous element from the
|
||||
// next.
|
||||
if (any) {
|
||||
if (value == ",") {
|
||||
value = lex();
|
||||
if (value == "]") {
|
||||
// Unexpected trailing `,` in array literal.
|
||||
abort();
|
||||
}
|
||||
} else {
|
||||
// A `,` must separate each array element.
|
||||
abort();
|
||||
}
|
||||
}
|
||||
// Elisions and leading commas are not permitted.
|
||||
if (value == ",") {
|
||||
abort();
|
||||
}
|
||||
results.push(get(value));
|
||||
}
|
||||
return results;
|
||||
} else if (value == "{") {
|
||||
// Parses a JSON object, returning a new JavaScript object.
|
||||
results = {};
|
||||
for (;; any || (any = true)) {
|
||||
value = lex();
|
||||
// A closing curly brace marks the end of the object literal.
|
||||
if (value == "}") {
|
||||
break;
|
||||
}
|
||||
// If the object literal contains members, the current token
|
||||
// should be a comma separator.
|
||||
if (any) {
|
||||
if (value == ",") {
|
||||
value = lex();
|
||||
if (value == "}") {
|
||||
// Unexpected trailing `,` in object literal.
|
||||
abort();
|
||||
}
|
||||
} else {
|
||||
// A `,` must separate each object member.
|
||||
abort();
|
||||
}
|
||||
}
|
||||
// Leading commas are not permitted, object property names must be
|
||||
// double-quoted strings, and a `:` must separate each property
|
||||
// name and value.
|
||||
if (value == "," || typeof value != "string" || value.charAt(0) != "@" || lex() != ":") {
|
||||
abort();
|
||||
}
|
||||
results[value.slice(1)] = get(lex());
|
||||
}
|
||||
return results;
|
||||
}
|
||||
// Unexpected token encountered.
|
||||
abort();
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
// Internal: Updates a traversed object member.
|
||||
update = function(source, property, callback) {
|
||||
var element = walk(source, property, callback);
|
||||
if (element === undef) {
|
||||
delete source[property];
|
||||
} else {
|
||||
source[property] = element;
|
||||
}
|
||||
};
|
||||
|
||||
// Internal: Recursively traverses a parsed JSON object, invoking the
|
||||
// `callback` function for each value. This is an implementation of the
|
||||
// `Walk(holder, name)` operation defined in ES 5.1 section 15.12.2.
|
||||
walk = function (source, property, callback) {
|
||||
var value = source[property], length;
|
||||
if (typeof value == "object" && value) {
|
||||
if (getClass.call(value) == "[object Array]") {
|
||||
for (length = value.length; length--;) {
|
||||
update(value, length, callback);
|
||||
}
|
||||
} else {
|
||||
// `forEach` can't be used to traverse an array in Opera <= 8.54,
|
||||
// as `Object#hasOwnProperty` returns `false` for array indices
|
||||
// (e.g., `![1, 2, 3].hasOwnProperty("0")`).
|
||||
forEach(value, function (property) {
|
||||
update(value, property, callback);
|
||||
});
|
||||
}
|
||||
}
|
||||
return callback.call(source, property, value);
|
||||
};
|
||||
|
||||
// Public: `JSON.parse`. See ES 5.1 section 15.12.2.
|
||||
JSON3.parse = function (source, callback) {
|
||||
var result, value;
|
||||
Index = 0;
|
||||
Source = source;
|
||||
result = get(lex());
|
||||
// If a JSON string contains multiple tokens, it is invalid.
|
||||
if (lex() != "$") {
|
||||
abort();
|
||||
}
|
||||
// Reset the parser state.
|
||||
Index = Source = null;
|
||||
return callback && getClass.call(callback) == "[object Function]" ? walk((value = {}, value[""] = result, value), "", callback) : result;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Export for asynchronous module loaders.
|
||||
if (isLoader) {
|
||||
define(function () {
|
||||
return JSON3;
|
||||
});
|
||||
}
|
||||
}).call(this);
|
||||
670
vendor/underscore/test/vendor/jslitmus.js
vendored
670
vendor/underscore/test/vendor/jslitmus.js
vendored
@@ -1,670 +0,0 @@
|
||||
// JSLitmus.js
|
||||
//
|
||||
// History:
|
||||
// 2008-10-27: Initial release
|
||||
// 2008-11-09: Account for iteration loop overhead
|
||||
// 2008-11-13: Added OS detection
|
||||
// 2009-02-25: Create tinyURL automatically, shift-click runs tests in reverse
|
||||
//
|
||||
// Copyright (c) 2008-2009, Robert Kieffer
|
||||
// All Rights Reserved
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the
|
||||
// Software), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
(function() {
|
||||
// Private methods and state
|
||||
|
||||
// Get platform info but don't go crazy trying to recognize everything
|
||||
// that's out there. This is just for the major platforms and OSes.
|
||||
var platform = 'unknown platform', ua = navigator.userAgent;
|
||||
|
||||
// Detect OS
|
||||
var oses = ['Windows','iPhone OS','(Intel |PPC )?Mac OS X','Linux'].join('|');
|
||||
var pOS = new RegExp('((' + oses + ') [^ \);]*)').test(ua) ? RegExp.$1 : null;
|
||||
if (!pOS) pOS = new RegExp('((' + oses + ')[^ \);]*)').test(ua) ? RegExp.$1 : null;
|
||||
|
||||
// Detect browser
|
||||
var pName = /(Chrome|MSIE|Safari|Opera|Firefox)/.test(ua) ? RegExp.$1 : null;
|
||||
|
||||
// Detect version
|
||||
var vre = new RegExp('(Version|' + pName + ')[ \/]([^ ;]*)');
|
||||
var pVersion = (pName && vre.test(ua)) ? RegExp.$2 : null;
|
||||
var platform = (pOS && pName && pVersion) ? pName + ' ' + pVersion + ' on ' + pOS : 'unknown platform';
|
||||
|
||||
/**
|
||||
* A smattering of methods that are needed to implement the JSLitmus testbed.
|
||||
*/
|
||||
var jsl = {
|
||||
/**
|
||||
* Enhanced version of escape()
|
||||
*/
|
||||
escape: function(s) {
|
||||
s = s.replace(/,/g, '\\,');
|
||||
s = escape(s);
|
||||
s = s.replace(/\+/g, '%2b');
|
||||
s = s.replace(/ /g, '+');
|
||||
return s;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get an element by ID.
|
||||
*/
|
||||
$: function(id) {
|
||||
return document.getElementById(id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Null function
|
||||
*/
|
||||
F: function() {},
|
||||
|
||||
/**
|
||||
* Set the status shown in the UI
|
||||
*/
|
||||
status: function(msg) {
|
||||
var el = jsl.$('jsl_status');
|
||||
if (el) el.innerHTML = msg || '';
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert a number to an abbreviated string like, "15K" or "10M"
|
||||
*/
|
||||
toLabel: function(n) {
|
||||
if (n == Infinity) {
|
||||
return 'Infinity';
|
||||
} else if (n > 1e9) {
|
||||
n = Math.round(n/1e8);
|
||||
return n/10 + 'B';
|
||||
} else if (n > 1e6) {
|
||||
n = Math.round(n/1e5);
|
||||
return n/10 + 'M';
|
||||
} else if (n > 1e3) {
|
||||
n = Math.round(n/1e2);
|
||||
return n/10 + 'K';
|
||||
}
|
||||
return n;
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy properties from src to dst
|
||||
*/
|
||||
extend: function(dst, src) {
|
||||
for (var k in src) dst[k] = src[k]; return dst;
|
||||
},
|
||||
|
||||
/**
|
||||
* Like Array.join(), but for the key-value pairs in an object
|
||||
*/
|
||||
join: function(o, delimit1, delimit2) {
|
||||
if (o.join) return o.join(delimit1); // If it's an array
|
||||
var pairs = [];
|
||||
for (var k in o) pairs.push(k + delimit1 + o[k]);
|
||||
return pairs.join(delimit2);
|
||||
},
|
||||
|
||||
/**
|
||||
* Array#indexOf isn't supported in IE, so we use this as a cross-browser solution
|
||||
*/
|
||||
indexOf: function(arr, o) {
|
||||
if (arr.indexOf) return arr.indexOf(o);
|
||||
for (var i = 0; i < this.length; i++) if (arr[i] === o) return i;
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Test manages a single test (created with
|
||||
* JSLitmus.test())
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
var Test = function (name, f) {
|
||||
if (!f) throw new Error('Undefined test function');
|
||||
if (!(/function[^\(]*\(([^,\)]*)/).test(f.toString())) {
|
||||
throw new Error('"' + name + '" test: Test is not a valid Function object');
|
||||
}
|
||||
this.loopArg = RegExp.$1;
|
||||
this.name = name;
|
||||
this.f = f;
|
||||
};
|
||||
|
||||
jsl.extend(Test, /** @lends Test */ {
|
||||
/** Calibration tests for establishing iteration loop overhead */
|
||||
CALIBRATIONS: [
|
||||
new Test('calibrating loop', function(count) {while (count--);}),
|
||||
new Test('calibrating function', jsl.F)
|
||||
],
|
||||
|
||||
/**
|
||||
* Run calibration tests. Returns true if calibrations are not yet
|
||||
* complete (in which case calling code should run the tests yet again).
|
||||
* onCalibrated - Callback to invoke when calibrations have finished
|
||||
*/
|
||||
calibrate: function(onCalibrated) {
|
||||
for (var i = 0; i < Test.CALIBRATIONS.length; i++) {
|
||||
var cal = Test.CALIBRATIONS[i];
|
||||
if (cal.running) return true;
|
||||
if (!cal.count) {
|
||||
cal.isCalibration = true;
|
||||
cal.onStop = onCalibrated;
|
||||
//cal.MIN_TIME = .1; // Do calibrations quickly
|
||||
cal.run(2e4);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
jsl.extend(Test.prototype, {/** @lends Test.prototype */
|
||||
/** Initial number of iterations */
|
||||
INIT_COUNT: 10,
|
||||
/** Max iterations allowed (i.e. used to detect bad looping functions) */
|
||||
MAX_COUNT: 1e9,
|
||||
/** Minimum time a test should take to get valid results (secs) */
|
||||
MIN_TIME: .5,
|
||||
|
||||
/** Callback invoked when test state changes */
|
||||
onChange: jsl.F,
|
||||
|
||||
/** Callback invoked when test is finished */
|
||||
onStop: jsl.F,
|
||||
|
||||
/**
|
||||
* Reset test state
|
||||
*/
|
||||
reset: function() {
|
||||
delete this.count;
|
||||
delete this.time;
|
||||
delete this.running;
|
||||
delete this.error;
|
||||
},
|
||||
|
||||
/**
|
||||
* Run the test (in a timeout). We use a timeout to make sure the browser
|
||||
* has a chance to finish rendering any UI changes we've made, like
|
||||
* updating the status message.
|
||||
*/
|
||||
run: function(count) {
|
||||
count = count || this.INIT_COUNT;
|
||||
jsl.status(this.name + ' x ' + count);
|
||||
this.running = true;
|
||||
var me = this;
|
||||
setTimeout(function() {me._run(count);}, 200);
|
||||
},
|
||||
|
||||
/**
|
||||
* The nuts and bolts code that actually runs a test
|
||||
*/
|
||||
_run: function(count) {
|
||||
var me = this;
|
||||
|
||||
// Make sure calibration tests have run
|
||||
if (!me.isCalibration && Test.calibrate(function() {me.run(count);})) return;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
var start, f = this.f, now, i = count;
|
||||
|
||||
// Start the timer
|
||||
start = new Date();
|
||||
|
||||
// Now for the money shot. If this is a looping function ...
|
||||
if (this.loopArg) {
|
||||
// ... let it do the iteration itself
|
||||
f(count);
|
||||
} else {
|
||||
// ... otherwise do the iteration for it
|
||||
while (i--) f();
|
||||
}
|
||||
|
||||
// Get time test took (in secs)
|
||||
this.time = Math.max(1,new Date() - start)/1000;
|
||||
|
||||
// Store iteration count and per-operation time taken
|
||||
this.count = count;
|
||||
this.period = this.time/count;
|
||||
|
||||
// Do we need to do another run?
|
||||
this.running = this.time <= this.MIN_TIME;
|
||||
|
||||
// ... if so, compute how many times we should iterate
|
||||
if (this.running) {
|
||||
// Bump the count to the nearest power of 2
|
||||
var x = this.MIN_TIME/this.time;
|
||||
var pow = Math.pow(2, Math.max(1, Math.ceil(Math.log(x)/Math.log(2))));
|
||||
count *= pow;
|
||||
if (count > this.MAX_COUNT) {
|
||||
throw new Error('Max count exceeded. If this test uses a looping function, make sure the iteration loop is working properly.');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Exceptions are caught and displayed in the test UI
|
||||
this.reset();
|
||||
this.error = e;
|
||||
}
|
||||
|
||||
// Figure out what to do next
|
||||
if (this.running) {
|
||||
me.run(count);
|
||||
} else {
|
||||
jsl.status('');
|
||||
me.onStop(me);
|
||||
}
|
||||
|
||||
// Finish up
|
||||
this.onChange(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the number of operations per second for this test.
|
||||
*
|
||||
* @param normalize if true, iteration loop overhead taken into account
|
||||
*/
|
||||
getHz: function(/**Boolean*/ normalize) {
|
||||
var p = this.period;
|
||||
|
||||
// Adjust period based on the calibration test time
|
||||
if (normalize && !this.isCalibration) {
|
||||
var cal = Test.CALIBRATIONS[this.loopArg ? 0 : 1];
|
||||
|
||||
// If the period is within 20% of the calibration time, then zero the
|
||||
// it out
|
||||
p = p < cal.period*1.2 ? 0 : p - cal.period;
|
||||
}
|
||||
|
||||
return Math.round(1/p);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a friendly string describing the test
|
||||
*/
|
||||
toString: function() {
|
||||
return this.name + ' - ' + this.time/this.count + ' secs';
|
||||
}
|
||||
});
|
||||
|
||||
// CSS we need for the UI
|
||||
var STYLESHEET = '<style> \
|
||||
#jslitmus {font-family:sans-serif; font-size: 12px;} \
|
||||
#jslitmus a {text-decoration: none;} \
|
||||
#jslitmus a:hover {text-decoration: underline;} \
|
||||
#jsl_status { \
|
||||
margin-top: 10px; \
|
||||
font-size: 10px; \
|
||||
color: #888; \
|
||||
} \
|
||||
A IMG {border:none} \
|
||||
#test_results { \
|
||||
margin-top: 10px; \
|
||||
font-size: 12px; \
|
||||
font-family: sans-serif; \
|
||||
border-collapse: collapse; \
|
||||
border-spacing: 0px; \
|
||||
} \
|
||||
#test_results th, #test_results td { \
|
||||
border: solid 1px #ccc; \
|
||||
vertical-align: top; \
|
||||
padding: 3px; \
|
||||
} \
|
||||
#test_results th { \
|
||||
vertical-align: bottom; \
|
||||
background-color: #ccc; \
|
||||
padding: 1px; \
|
||||
font-size: 10px; \
|
||||
} \
|
||||
#test_results #test_platform { \
|
||||
color: #444; \
|
||||
text-align:center; \
|
||||
} \
|
||||
#test_results .test_row { \
|
||||
color: #006; \
|
||||
cursor: pointer; \
|
||||
} \
|
||||
#test_results .test_nonlooping { \
|
||||
border-left-style: dotted; \
|
||||
border-left-width: 2px; \
|
||||
} \
|
||||
#test_results .test_looping { \
|
||||
border-left-style: solid; \
|
||||
border-left-width: 2px; \
|
||||
} \
|
||||
#test_results .test_name {white-space: nowrap;} \
|
||||
#test_results .test_pending { \
|
||||
} \
|
||||
#test_results .test_running { \
|
||||
font-style: italic; \
|
||||
} \
|
||||
#test_results .test_done {} \
|
||||
#test_results .test_done { \
|
||||
text-align: right; \
|
||||
font-family: monospace; \
|
||||
} \
|
||||
#test_results .test_error {color: #600;} \
|
||||
#test_results .test_error .error_head {font-weight:bold;} \
|
||||
#test_results .test_error .error_body {font-size:85%;} \
|
||||
#test_results .test_row:hover td { \
|
||||
background-color: #ffc; \
|
||||
text-decoration: underline; \
|
||||
} \
|
||||
#chart { \
|
||||
margin: 10px 0px; \
|
||||
width: 250px; \
|
||||
} \
|
||||
#chart img { \
|
||||
border: solid 1px #ccc; \
|
||||
margin-bottom: 5px; \
|
||||
} \
|
||||
#chart #tiny_url { \
|
||||
height: 40px; \
|
||||
width: 250px; \
|
||||
} \
|
||||
#jslitmus_credit { \
|
||||
font-size: 10px; \
|
||||
color: #888; \
|
||||
margin-top: 8px; \
|
||||
} \
|
||||
</style>';
|
||||
|
||||
// HTML markup for the UI
|
||||
var MARKUP = '<div id="jslitmus"> \
|
||||
<button onclick="JSLitmus.runAll(event)">Run Tests</button> \
|
||||
<button id="stop_button" disabled="disabled" onclick="JSLitmus.stop()">Stop Tests</button> \
|
||||
<br \> \
|
||||
<br \> \
|
||||
<input type="checkbox" style="vertical-align: middle" id="test_normalize" checked="checked" onchange="JSLitmus.renderAll()""> Normalize results \
|
||||
<table id="test_results"> \
|
||||
<colgroup> \
|
||||
<col /> \
|
||||
<col width="100" /> \
|
||||
</colgroup> \
|
||||
<tr><th id="test_platform" colspan="2">' + platform + '</th></tr> \
|
||||
<tr><th>Test</th><th>Ops/sec</th></tr> \
|
||||
<tr id="test_row_template" class="test_row" style="display:none"> \
|
||||
<td class="test_name"></td> \
|
||||
<td class="test_result">Ready</td> \
|
||||
</tr> \
|
||||
</table> \
|
||||
<div id="jsl_status"></div> \
|
||||
<div id="chart" style="display:none"> \
|
||||
<a id="chart_link" target="_blank"><img id="chart_image"></a> \
|
||||
TinyURL (for chart): \
|
||||
<iframe id="tiny_url" frameBorder="0" scrolling="no" src=""></iframe> \
|
||||
</div> \
|
||||
<a id="jslitmus_credit" title="JSLitmus home page" href="http://code.google.com/p/jslitmus" target="_blank">Powered by JSLitmus</a> \
|
||||
</div>';
|
||||
|
||||
/**
|
||||
* The public API for creating and running tests
|
||||
*/
|
||||
window.JSLitmus = {
|
||||
/** The list of all tests that have been registered with JSLitmus.test */
|
||||
_tests: [],
|
||||
/** The queue of tests that need to be run */
|
||||
_queue: [],
|
||||
|
||||
/**
|
||||
* The parsed query parameters the current page URL. This is provided as a
|
||||
* convenience for test functions - it's not used by JSLitmus proper
|
||||
*/
|
||||
params: {},
|
||||
|
||||
/**
|
||||
* Initialize
|
||||
*/
|
||||
_init: function() {
|
||||
// Parse query params into JSLitmus.params[] hash
|
||||
var match = (location + '').match(/([^?#]*)(#.*)?$/);
|
||||
if (match) {
|
||||
var pairs = match[1].split('&');
|
||||
for (var i = 0; i < pairs.length; i++) {
|
||||
var pair = pairs[i].split('=');
|
||||
if (pair.length > 1) {
|
||||
var key = pair.shift();
|
||||
var value = pair.length > 1 ? pair.join('=') : pair[0];
|
||||
this.params[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write out the stylesheet. We have to do this here because IE
|
||||
// doesn't honor sheets written after the document has loaded.
|
||||
document.write(STYLESHEET);
|
||||
|
||||
// Setup the rest of the UI once the document is loaded
|
||||
if (window.addEventListener) {
|
||||
window.addEventListener('load', this._setup, false);
|
||||
} else if (document.addEventListener) {
|
||||
document.addEventListener('load', this._setup, false);
|
||||
} else if (window.attachEvent) {
|
||||
window.attachEvent('onload', this._setup);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set up the UI
|
||||
*/
|
||||
_setup: function() {
|
||||
var el = jsl.$('jslitmus_container');
|
||||
if (!el) document.body.appendChild(el = document.createElement('div'));
|
||||
|
||||
el.innerHTML = MARKUP;
|
||||
|
||||
// Render the UI for all our tests
|
||||
for (var i=0; i < JSLitmus._tests.length; i++)
|
||||
JSLitmus.renderTest(JSLitmus._tests[i]);
|
||||
},
|
||||
|
||||
/**
|
||||
* (Re)render all the test results
|
||||
*/
|
||||
renderAll: function() {
|
||||
for (var i = 0; i < JSLitmus._tests.length; i++)
|
||||
JSLitmus.renderTest(JSLitmus._tests[i]);
|
||||
JSLitmus.renderChart();
|
||||
},
|
||||
|
||||
/**
|
||||
* (Re)render the chart graphics
|
||||
*/
|
||||
renderChart: function() {
|
||||
var url = JSLitmus.chartUrl();
|
||||
jsl.$('chart_link').href = url;
|
||||
jsl.$('chart_image').src = url;
|
||||
jsl.$('chart').style.display = '';
|
||||
|
||||
// Update the tiny URL
|
||||
jsl.$('tiny_url').src = 'http://tinyurl.com/api-create.php?url='+escape(url);
|
||||
},
|
||||
|
||||
/**
|
||||
* (Re)render the results for a specific test
|
||||
*/
|
||||
renderTest: function(test) {
|
||||
// Make a new row if needed
|
||||
if (!test._row) {
|
||||
var trow = jsl.$('test_row_template');
|
||||
if (!trow) return;
|
||||
|
||||
test._row = trow.cloneNode(true);
|
||||
test._row.style.display = '';
|
||||
test._row.id = '';
|
||||
test._row.onclick = function() {JSLitmus._queueTest(test);};
|
||||
test._row.title = 'Run ' + test.name + ' test';
|
||||
trow.parentNode.appendChild(test._row);
|
||||
test._row.cells[0].innerHTML = test.name;
|
||||
}
|
||||
|
||||
var cell = test._row.cells[1];
|
||||
var cns = [test.loopArg ? 'test_looping' : 'test_nonlooping'];
|
||||
|
||||
if (test.error) {
|
||||
cns.push('test_error');
|
||||
cell.innerHTML =
|
||||
'<div class="error_head">' + test.error + '</div>' +
|
||||
'<ul class="error_body"><li>' +
|
||||
jsl.join(test.error, ': ', '</li><li>') +
|
||||
'</li></ul>';
|
||||
} else {
|
||||
if (test.running) {
|
||||
cns.push('test_running');
|
||||
cell.innerHTML = 'running';
|
||||
} else if (jsl.indexOf(JSLitmus._queue, test) >= 0) {
|
||||
cns.push('test_pending');
|
||||
cell.innerHTML = 'pending';
|
||||
} else if (test.count) {
|
||||
cns.push('test_done');
|
||||
var hz = test.getHz(jsl.$('test_normalize').checked);
|
||||
cell.innerHTML = hz != Infinity ? hz : '∞';
|
||||
} else {
|
||||
cell.innerHTML = 'ready';
|
||||
}
|
||||
}
|
||||
cell.className = cns.join(' ');
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new test
|
||||
*/
|
||||
test: function(name, f) {
|
||||
// Create the Test object
|
||||
var test = new Test(name, f);
|
||||
JSLitmus._tests.push(test);
|
||||
|
||||
// Re-render if the test state changes
|
||||
test.onChange = JSLitmus.renderTest;
|
||||
|
||||
// Run the next test if this one finished
|
||||
test.onStop = function(test) {
|
||||
if (JSLitmus.onTestFinish) JSLitmus.onTestFinish(test);
|
||||
JSLitmus.currentTest = null;
|
||||
JSLitmus._nextTest();
|
||||
};
|
||||
|
||||
// Render the new test
|
||||
this.renderTest(test);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add all tests to the run queue
|
||||
*/
|
||||
runAll: function(e) {
|
||||
e = e || window.event;
|
||||
var reverse = e && e.shiftKey, len = JSLitmus._tests.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
JSLitmus._queueTest(JSLitmus._tests[!reverse ? i : (len - i - 1)]);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove all tests from the run queue. The current test has to finish on
|
||||
* it's own though
|
||||
*/
|
||||
stop: function() {
|
||||
while (JSLitmus._queue.length) {
|
||||
var test = JSLitmus._queue.shift();
|
||||
JSLitmus.renderTest(test);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Run the next test in the run queue
|
||||
*/
|
||||
_nextTest: function() {
|
||||
if (!JSLitmus.currentTest) {
|
||||
var test = JSLitmus._queue.shift();
|
||||
if (test) {
|
||||
jsl.$('stop_button').disabled = false;
|
||||
JSLitmus.currentTest = test;
|
||||
test.run();
|
||||
JSLitmus.renderTest(test);
|
||||
if (JSLitmus.onTestStart) JSLitmus.onTestStart(test);
|
||||
} else {
|
||||
jsl.$('stop_button').disabled = true;
|
||||
JSLitmus.renderChart();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a test to the run queue
|
||||
*/
|
||||
_queueTest: function(test) {
|
||||
if (jsl.indexOf(JSLitmus._queue, test) >= 0) return;
|
||||
JSLitmus._queue.push(test);
|
||||
JSLitmus.renderTest(test);
|
||||
JSLitmus._nextTest();
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate a Google Chart URL that shows the data for all tests
|
||||
*/
|
||||
chartUrl: function() {
|
||||
var n = JSLitmus._tests.length, markers = [], data = [];
|
||||
var d, min = 0, max = -1e10;
|
||||
var normalize = jsl.$('test_normalize').checked;
|
||||
|
||||
// Gather test data
|
||||
for (var i=0; i < JSLitmus._tests.length; i++) {
|
||||
var test = JSLitmus._tests[i];
|
||||
if (test.count) {
|
||||
var hz = test.getHz(normalize);
|
||||
var v = hz != Infinity ? hz : 0;
|
||||
data.push(v);
|
||||
markers.push('t' + jsl.escape(test.name + '(' + jsl.toLabel(hz)+ ')') + ',000000,0,' +
|
||||
markers.length + ',10');
|
||||
max = Math.max(v, max);
|
||||
}
|
||||
}
|
||||
if (markers.length <= 0) return null;
|
||||
|
||||
// Build chart title
|
||||
var title = document.getElementsByTagName('title');
|
||||
title = (title && title.length) ? title[0].innerHTML : null;
|
||||
var chart_title = [];
|
||||
if (title) chart_title.push(title);
|
||||
chart_title.push('Ops/sec (' + platform + ')');
|
||||
|
||||
// Build labels
|
||||
var labels = [jsl.toLabel(min), jsl.toLabel(max)];
|
||||
|
||||
var w = 250, bw = 15;
|
||||
var bs = 5;
|
||||
var h = markers.length*(bw + bs) + 30 + chart_title.length*20;
|
||||
|
||||
var params = {
|
||||
chtt: escape(chart_title.join('|')),
|
||||
chts: '000000,10',
|
||||
cht: 'bhg', // chart type
|
||||
chd: 't:' + data.join(','), // data set
|
||||
chds: min + ',' + max, // max/min of data
|
||||
chxt: 'x', // label axes
|
||||
chxl: '0:|' + labels.join('|'), // labels
|
||||
chsp: '0,1',
|
||||
chm: markers.join('|'), // test names
|
||||
chbh: [bw, 0, bs].join(','), // bar widths
|
||||
// chf: 'bg,lg,0,eeeeee,0,eeeeee,.5,ffffff,1', // gradient
|
||||
chs: w + 'x' + h
|
||||
};
|
||||
return 'http://chart.apis.google.com/chart?' + jsl.join(params, '=', '&');
|
||||
}
|
||||
};
|
||||
|
||||
JSLitmus._init();
|
||||
})();
|
||||
Reference in New Issue
Block a user