function Timer() { let ms = performance.now(); this.start = function() { return ms = performance.now(); }; this.elapsedMs = function() { return performance.now() - ms; }; } function Buffer() { let strings = []; this.write = function(object) { strings.push(object.toString()); }; this.writeln = function(object) { this.write(object); this.write("\n"); }; this.flush = function() { strings = []; }; this.toString = function() { return strings.join(""); }; } function FakeBuffer() { this.write = function(object) { }; this.writeln = function(object) { }; this.flush = function() { }; this.toString = function() { return "Fake Buffer"; }; } function Reader(readMethod, name) { const that = this; const timer = new Timer(); const stats = new Stats(); const readStats = new IOStats(stats, "read"); this.read = function(args) { timer.start(); const contents = readMethod(args); stats.add(timer.elapsedMs()); return contents; }; this.getReadStats = function() { return readStats; }; this.toString = function() { return name + "\n" + readStats.toString(); }; } function Writer(writeMethod, name) { const that = this; const timer = new Timer(); const stats = new Stats(); const writeStats = new IOStats(stats, "write"); this.write = function(contents) { const text = contents !== undefined && contents !== null ? contents.toString() : ""; timer.start(); writeMethod(text); stats.add(timer.elapsedMs()); }; this.getWriteStats = function() { return writeStats; }; this.toString = function() { return name + "\n" + writeStats.toString(); }; } function ReaderWriter(readMethod, writeMethod, name) { Reader.call(this, readMethod, name); Writer.call(this, writeMethod, name); this.toString = function() { sb = new Buffer(); sb.writeln(name); sb.write(this.getReadStats()); sb.write(this.getWriteStats()); return sb.toString(); } } function Stats() { const that = this; const UNDEFINED = "undefined"; const dataValues = []; let sum, min, max, variance, sd; this.add = function(value) { dataValues.push(value); if (dataValues.length === 1) { sum = min = max = value; } else { sum += value; if (value < min) { min = value; } else if (value > max) { max = value; } } variance = sd = null; }; this.count = function() { return dataValues.length; }; this.sum = function() { return this.count() === 0 ? UNDEFINED : sum; }; this.minimum = function() { return this.count() === 0 ? UNDEFINED : min; }; this.maximum = function() { return this.count() === 0 ? UNDEFINED : max; }; this.average = function() { return this.count() === 0 ? UNDEFINED : sum / dataValues.length; }; this.variance = function() { if (this.count() === 0) { return UNDEFINED; } else if (variance === null) { variance = calculateVariance(); } return variance; }; this.standardDeviation = function() { if (this.count() === 0) { return UNDEFINED; } else if (sd === null) { sd = calculateStandardDeviation(); } return sd; }; function calculateVariance() { Stats.debugger.writeln("calculateVariance:"); const n = dataValues.length; Stats.debugger.writeln(" n: " + n); let sumOfSquares = 0; let avg = that.average(); Stats.debugger.writeln(" avg: " + avg); for (let i = 0; i < n; i++) { const diff = dataValues[i] - avg; Stats.debugger.writeln(" diff: " + diff); const square = diff * diff; Stats.debugger.writeln(" square: " + square); sumOfSquares += square; } return sumOfSquares / n; } function calculateStandardDeviation() { return Math.sqrt(that.variance()); } } Stats.debugger = new FakeBuffer(); function IOStats(stats, label) { this.count = function() { return stats.count(); }; this.sumMs = function() { return stats.sum(); }; this.minMs = function() { return stats.minimum(); }; this.maxMs = function() { return stats.maximum(); }; this.avgMs = function() { return stats.average(); }; this.sdMs = function() { return stats.standardDeviation(); }; this.toString = function() { const sb = new Buffer(); sb.write(" " + label + "s: "); sb.writeln(this.count()); sb.write(" sum (ms): "); sb.writeln(this.sumMs()); sb.write(" min (ms): "); sb.writeln(this.minMs()); sb.write(" max (ms): "); sb.writeln(this.maxMs()); sb.write(" avg (ms): "); sb.writeln(this.avgMs()); sb.write(" sd (ms): "); sb.writeln(this.sdMs()); return sb.toString(); } } const Body = { writePreHTML: function(contents) { const list = document.getElementsByTagName("body"); let i = list.length; while (i-- > 0) { list[i].innerHTML = "<pre>" + contents.toString() + "</pre>"; } }, readInnerText: function() { const sb = new Buffer(); const list = document.getElementsByTagName("body"); let i = list.length; while (i-- > 0) { sb.write(list[i].innerText); } return sb.toString(); } }; function Journal() { const contents = new Buffer(); this.read = function() { return contents.toString(); }; this.write = function(text) { contents.write(text); }; this.clear = function() { contents.flush(); }; } const IOJournal = new Journal(); const IO = { prompt: new Reader(prompt, "prompt"), body: new ReaderWriter(Body.readInnerText, Body.writePreHTML, "body"), journal: new ReaderWriter(IOJournal.read, IOJournal.write, "journal"), console: new Writer(console.log, "console"), alert: new Writer(alert, "alert"), broadcast: function(message) { this.body.write(message); this.journal.write(message); this.console.write(message); this.alert.write(message); }, toString: function() { const sb = new Buffer(); sb.writeln(this.prompt); sb.writeln(this.body); sb.writeln(this.journal); sb.writeln(this.console); sb.writeln(this.alert); return sb.toString(); } };