summaryrefslogtreecommitdiff
path: root/src/toys/turing/js/tape.js
blob: fd05366f82ddce7c3c80242144b421d2919b9b83 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
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;
  }
}