Pages

JavaScript IO

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