const ESCAPE_REGEX = /[.*+?^${}()|[\]\\]/g; function escapeForRegex(value) { return value.replace(ESCAPE_REGEX, "\\$&"); } export class Tape { constructor({ initialContent = "", blankSymbol = "B", minLength = 50, padding = 40 } = {}) { this.blankSymbol = blankSymbol; this.minLength = minLength; this.padding = padding; this.reset(initialContent); } reset(initialContent = "") { const targetLength = Math.max(this.minLength, initialContent.length + this.padding); this.cells = Array(targetLength).fill(this.blankSymbol); const startOffset = Math.floor((targetLength - initialContent.length) / 2); for (let i = 0; i < initialContent.length; i++) { this.cells[startOffset + i] = initialContent[i]; } this.headIndex = startOffset; } get length() { return this.cells.length; } getHeadIndex() { return this.headIndex; } readHead() { return this.getCell(this.headIndex); } writeHead(symbol) { this.cells[this.headIndex] = symbol || this.blankSymbol; } readAt(index) { return this.getCell(index); } writeAt(index, symbol) { if (index < 0) { throw new Error("Cannot write to a negative tape index"); } this.ensureRightCapacity(index); this.cells[index] = symbol || this.blankSymbol; } setHead(index) { if (index < 0) { throw new Error("Head index cannot be negative"); } this.ensureRightCapacity(index); this.headIndex = index; } moveLeft() { if (this.headIndex === 0) { this.cells.unshift(this.blankSymbol); } else { this.headIndex -= 1; return; } } moveRight() { this.headIndex += 1; if (this.headIndex >= this.cells.length) { this.cells.push(this.blankSymbol); } } getCell(index) { if (index < 0 || index >= this.cells.length) { return this.blankSymbol; } return this.cells[index]; } ensureRightCapacity(index) { while (index >= this.cells.length) { this.cells.push(this.blankSymbol); } } getContents({ trimTrailing = true } = {}) { let snapshot = this.cells.join(""); if (trimTrailing) { const regex = new RegExp(`${escapeForRegex(this.blankSymbol)}+$`, "g"); snapshot = snapshot.replace(regex, ""); } return snapshot; } }