Source code

Revision control

Copy as Markdown

Other Tools

/*
Author Alexander Akait @alexander-akait
*/
"use strict";
class LocConverter {
/**
* Creates an instance of LocConverter.
* @param {string} input input
*/
constructor(input) {
/** @type {string} */
this._input = input;
/** @type {number} */
this.line = 1;
/** @type {number} */
this.column = 0;
/** @type {number} */
this.pos = 0;
}
/**
* Returns location converter.
* @param {number} pos position
* @returns {LocConverter} location converter
*/
get(pos) {
if (this.pos !== pos) {
if (this.pos < pos) {
// Advance: delta-bounded scan of [this.pos, pos), allocation-free
// (unbounded index scans go quadratic on newline-free input).
const input = this._input;
let last = -1;
for (let j = this.pos; j < pos; j++) {
if (input.charCodeAt(j) === 10) {
this.line++;
last = j;
}
}
this.column =
last === -1 ? this.column + (pos - this.pos) : pos - last - 1;
} else {
// Retreat: count newlines crossed in (pos, this.pos), i.e.
// exclude the newline at `this.pos` itself. By convention a
// `\n` is the last column of its line, so when `this.pos`
// sits on one we're already on the line containing it; only
// newlines strictly **before** `this.pos` and at-or-after
// `pos` represent crossed line boundaries.
let i = this._input.lastIndexOf("\n", this.pos - 1);
while (i >= pos) {
this.line--;
i = i > 0 ? this._input.lastIndexOf("\n", i - 1) : -1;
}
this.column = i === -1 ? pos : pos - i - 1;
}
this.pos = pos;
}
return this;
}
}
module.exports = LocConverter;