'use strict'; /** * A streaming, character code-based string reader */ class StreamReader { constructor(string, start, end) { if (end == null && typeof string === 'string') { end = string.length; } this.string = string; this.pos = this.start = start || 0; this.end = end; } /** * Returns true only if the stream is at the end of the file. * @returns {Boolean} */ eof() { return this.pos >= this.end; } /** * Creates a new stream instance which is limited to given `start` and `end` * range. E.g. its `eof()` method will look at `end` property, not actual * stream end * @param {Point} start * @param {Point} end * @return {StreamReader} */ limit(start, end) { return new this.constructor(this.string, start, end); } /** * Returns the next character code in the stream without advancing it. * Will return NaN at the end of the file. * @returns {Number} */ peek() { return this.string.charCodeAt(this.pos); } /** * Returns the next character in the stream and advances it. * Also returns undefined when no more characters are available. * @returns {Number} */ next() { if (this.pos < this.string.length) { return this.string.charCodeAt(this.pos++); } } /** * `match` can be a character code or a function that takes a character code * and returns a boolean. If the next character in the stream 'matches' * the given argument, it is consumed and returned. * Otherwise, `false` is returned. * @param {Number|Function} match * @returns {Boolean} */ eat(match) { const ch = this.peek(); const ok = typeof match === 'function' ? match(ch) : ch === match; if (ok) { this.next(); } return ok; } /** * Repeatedly calls eat with the given argument, until it * fails. Returns true if any characters were eaten. * @param {Object} match * @returns {Boolean} */ eatWhile(match) { const start = this.pos; while (!this.eof() && this.eat(match)) {} return this.pos !== start; } /** * Backs up the stream n characters. Backing it up further than the * start of the current token will cause things to break, so be careful. * @param {Number} n */ backUp(n) { this.pos -= (n || 1); } /** * Get the string between the start of the current token and the * current stream position. * @returns {String} */ current() { return this.substring(this.start, this.pos); } /** * Returns substring for given range * @param {Number} start * @param {Number} [end] * @return {String} */ substring(start, end) { return this.string.slice(start, end); } /** * Creates error object with current stream state * @param {String} message * @return {Error} */ error(message) { const err = new Error(`${message} at char ${this.pos + 1}`); err.originalMessage = message; err.pos = this.pos; err.string = this.string; return err; } } module.exports = StreamReader;