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();
}
};